├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── css │ └── bisn-admin.css └── js │ ├── bisn-admin.js │ └── bisn.js ├── back-in-stock-notifications.php ├── bisn-woocommerce-my-account.php ├── classes ├── class-bisn-back-in-stock-email.php ├── class-bisn-data-helper.php ├── class-bisn-waitlist-table.php └── templates │ └── emails │ ├── back-in-stock-notification.php │ └── plain │ └── back-in-stock-notification.php ├── composer.json ├── composer.lock ├── includes └── helper-functions.php ├── languages ├── bisn-af.mo ├── bisn-af.po ├── bisn-es_MX.mo ├── bisn-es_MX.po ├── bisn-fr_FR.mo ├── bisn-fr_FR.po ├── bisn-it_IT.mo ├── bisn-it_IT.po └── bisn.pot └── vendor ├── autoload.php ├── composer ├── ClassLoader.php ├── InstalledVersions.php ├── LICENSE ├── autoload_classmap.php ├── autoload_namespaces.php ├── autoload_psr4.php ├── autoload_real.php ├── autoload_static.php ├── installed.json ├── installed.php └── platform_check.php ├── plugin-update-checker ├── Puc │ ├── v5 │ │ └── PucFactory.php │ └── v5p4 │ │ ├── Autoloader.php │ │ ├── DebugBar │ │ ├── Extension.php │ │ ├── Panel.php │ │ ├── PluginExtension.php │ │ ├── PluginPanel.php │ │ └── ThemePanel.php │ │ ├── InstalledPackage.php │ │ ├── Metadata.php │ │ ├── OAuthSignature.php │ │ ├── Plugin │ │ ├── Package.php │ │ ├── PluginInfo.php │ │ ├── Ui.php │ │ ├── Update.php │ │ └── UpdateChecker.php │ │ ├── PucFactory.php │ │ ├── Scheduler.php │ │ ├── StateStore.php │ │ ├── Theme │ │ ├── Package.php │ │ ├── Update.php │ │ └── UpdateChecker.php │ │ ├── Update.php │ │ ├── UpdateChecker.php │ │ ├── UpgraderStatus.php │ │ ├── Utils.php │ │ ├── Vcs │ │ ├── Api.php │ │ ├── BaseChecker.php │ │ ├── BitBucketApi.php │ │ ├── GitHubApi.php │ │ ├── GitLabApi.php │ │ ├── PluginUpdateChecker.php │ │ ├── Reference.php │ │ ├── ReleaseAssetSupport.php │ │ ├── ReleaseFilteringFeature.php │ │ ├── ThemeUpdateChecker.php │ │ └── VcsCheckerMethods.php │ │ └── WpCliCheckTrigger.php ├── README.md ├── composer.json ├── css │ └── puc-debug-bar.css ├── js │ └── debug-bar.js ├── languages │ ├── plugin-update-checker-ca.mo │ ├── plugin-update-checker-ca.po │ ├── plugin-update-checker-cs_CZ.mo │ ├── plugin-update-checker-cs_CZ.po │ ├── plugin-update-checker-da_DK.mo │ ├── plugin-update-checker-da_DK.po │ ├── plugin-update-checker-de_DE.mo │ ├── plugin-update-checker-de_DE.po │ ├── plugin-update-checker-es_AR.mo │ ├── plugin-update-checker-es_AR.po │ ├── plugin-update-checker-es_CL.mo │ ├── plugin-update-checker-es_CL.po │ ├── plugin-update-checker-es_CO.mo │ ├── plugin-update-checker-es_CO.po │ ├── plugin-update-checker-es_CR.mo │ ├── plugin-update-checker-es_CR.po │ ├── plugin-update-checker-es_DO.mo │ ├── plugin-update-checker-es_DO.po │ ├── plugin-update-checker-es_ES.mo │ ├── plugin-update-checker-es_ES.po │ ├── plugin-update-checker-es_GT.mo │ ├── plugin-update-checker-es_GT.po │ ├── plugin-update-checker-es_HN.mo │ ├── plugin-update-checker-es_HN.po │ ├── plugin-update-checker-es_MX.mo │ ├── plugin-update-checker-es_MX.po │ ├── plugin-update-checker-es_PE.mo │ ├── plugin-update-checker-es_PE.po │ ├── plugin-update-checker-es_PR.mo │ ├── plugin-update-checker-es_PR.po │ ├── plugin-update-checker-es_UY.mo │ ├── plugin-update-checker-es_UY.po │ ├── plugin-update-checker-es_VE.mo │ ├── plugin-update-checker-es_VE.po │ ├── plugin-update-checker-fa_IR.mo │ ├── plugin-update-checker-fa_IR.po │ ├── plugin-update-checker-fr_CA.mo │ ├── plugin-update-checker-fr_CA.po │ ├── plugin-update-checker-fr_FR.mo │ ├── plugin-update-checker-fr_FR.po │ ├── plugin-update-checker-hu_HU.mo │ ├── plugin-update-checker-hu_HU.po │ ├── plugin-update-checker-it_IT.mo │ ├── plugin-update-checker-it_IT.po │ ├── plugin-update-checker-ja.mo │ ├── plugin-update-checker-ja.po │ ├── plugin-update-checker-nl_BE.mo │ ├── plugin-update-checker-nl_BE.po │ ├── plugin-update-checker-nl_NL.mo │ ├── plugin-update-checker-nl_NL.po │ ├── plugin-update-checker-pt_BR.mo │ ├── plugin-update-checker-pt_BR.po │ ├── plugin-update-checker-ru_RU.mo │ ├── plugin-update-checker-ru_RU.po │ ├── plugin-update-checker-sl_SI.mo │ ├── plugin-update-checker-sl_SI.po │ ├── plugin-update-checker-sv_SE.mo │ ├── plugin-update-checker-sv_SE.po │ ├── plugin-update-checker-tr_TR.mo │ ├── plugin-update-checker-tr_TR.po │ ├── plugin-update-checker-uk_UA.mo │ ├── plugin-update-checker-uk_UA.po │ ├── plugin-update-checker-zh_CN.mo │ ├── plugin-update-checker-zh_CN.po │ └── plugin-update-checker.pot ├── license.txt ├── load-v5p4.php ├── plugin-update-checker.php └── vendor │ ├── Parsedown.php │ ├── ParsedownModern.php │ └── PucReadmeParser.php └── robertdevore └── wpcom-check ├── LICENSE ├── README.md ├── composer.json └── src └── WPComPluginHandler.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.0.2 (02/21/2025) 4 | 5 | * [📦 NEW: Added WPCom Check to restrict plugin usage on wordpress.com](https://github.com/robertdevore/back-in-stock-notifications/commit/7ec4f1ad19a92b066f814eab1500163ca7633dfa) 6 | * [📦 NEW: Added Spanish translation](https://github.com/robertdevore/back-in-stock-notifications/commit/4fc9fb368c078f35bf50560a1847726d467deefa) 7 | * [📦 NEW: Added French translation](https://github.com/robertdevore/back-in-stock-notifications/commit/3d871544a92f4acab6634131f88bb97ce2f6ea91) 8 | * [📦 NEW: Added Italian translation](https://github.com/robertdevore/back-in-stock-notifications/commit/c8cbd43abcbca43049ed9352270a1573709afbde) 9 | * [📦 NEW: Added Afrikaans translation](https://github.com/robertdevore/back-in-stock-notifications/commit/fa18356138d8e1ce0cf1816db9df38c950728749) 10 | * [👌 IMPROVE: General code cleanup](https://github.com/robertdevore/back-in-stock-notifications/commit/bef75f4c12c7c3f27753e96e4315a659ce0872e3) 11 | * [👌 IMPROVE: Updated text strings for localization](https://github.com/robertdevore/back-in-stock-notifications/commit/43937c0d5ff251b4a696872ffa7d286a70787ca4) 12 | 13 | ## 1.0.1 (11/05/2024) 14 | 15 | * Updated email notifications with batch processing for improved performance and scalability on large waitlists in `back-in-stock-notifications.php` and `classes/class-bisn-back-in-stock-email.php` 16 | * Updated CSV export with performance enhancements in `back-in-stock-notifications.php` 17 | 18 | ## 1.0.0 (10/31/2024) 19 | 20 | * Initial release -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Back In Stock Notifications for WooCommerce® 2 | 3 | Automatically notify customers when products they're interested in are back in stock, and track demand for your most popular items. The plugin integrates seamlessly with WooCommerce® to capture customer interest and manage notifications efficiently. 4 | 5 | * * * 6 | 7 | ## Features 8 | 9 | - **Automated Notifications**: Sends customizable emails to customers when a product they signed up for is back in stock. 10 | - **Demand Tracking**: Track and display demand trends through waitlist statistics and analytics. 11 | - **WooCommerce® Integration**: Built for WooCommerce® to manage back-in-stock notifications. 12 | - **Customizable Emails**: Email templates for customer notifications can be customized to match your brand. 13 | - **Dashboard Insights**: Gain insights into product demand, customer interest, and track notification history within your WooCommerce® admin. 14 | - **Automatic Updates**: Receive updates through the WordPress® dashboard using [PluginUpdateChecker](https://github.com/YahnisElsts/plugin-update-checker). 15 | * * * 16 | 17 | ## Installation 18 | 19 | ### Option 1: Install via WordPress® Dashboard (Recommended) 20 | 21 | 1. Download the latest `.zip` file from the [Releases section on GitHub](https://github.com/robertdevore/back-in-stock-notifications/releases). 22 | 2. In your WordPress® Dashboard, navigate to **Plugins > Add New**. 23 | 3. Click the **Upload Plugin** button at the top of the page. 24 | 4. Select the `.zip` file you downloaded and click **Install Now**. 25 | 5. Once installation is complete, click **Activate Plugin**. 26 | 27 | * * * 28 | 29 | ### Option 2: Install via SFTP 30 | 31 | 1. Download the latest `.zip` file from the [Releases section on GitHub](https://github.com/robertdevore/back-in-stock-notifications/releases). 32 | 2. Unzip the file to create a folder named `back-in-stock-notifications`. 33 | 3. Use an SFTP client (e.g., FileZilla, Cyberduck) to connect to your WordPress® server. 34 | 4. Upload the `back-in-stock-notifications` folder to the `/wp-content/plugins/` directory. 35 | 5. Go to your WordPress® Dashboard, navigate to **Plugins**, and click **Activate** under _Back In Stock Notifications for WooCommerce®. 36 | 37 | ### Requirements 38 | 39 | - WooCommerce® (must be active for the plugin to work) 40 | - WordPress® 5.0+ 41 | - PHP 7.2+ (compatible with PHP 8.0+) 42 | * * * 43 | 44 | ## Configuration and Setup 45 | 46 | ### Step 1: Enable WooCommerce® Compatibility 47 | 48 | Ensure WooCommerce® is installed and activated before using this plugin. The plugin will automatically deactivate if WooCommerce® is not active. 49 | 50 | ### Step 2: Configure the Plugin 51 | 52 | Upon activation, the plugin creates the following custom database tables: 53 | 54 | - **Waitlist**: Tracks product waitlists by customer email. 55 | - **Waitlist History**: Records historical waitlist data. 56 | - **Notifications**: Stores sent notifications for back-in-stock alerts. 57 | 58 | These tables are created and maintained automatically. 59 | 60 | ### Step 3: Email Customization 61 | 62 | The plugin includes customizable email templates located in: 63 | 64 | - `templates/emails/back-in-stock-notification.php`: HTML version of the back-in-stock email. 65 | - `templates/emails/plain/back-in-stock-notification.php`: Plain text version of the email. 66 | 67 | You can further customize these templates by copying them to your theme folder under `woocommerce/emails/` and editing them to match your brand style. 68 | 69 | * * * 70 | 71 | ## Usage 72 | 73 | ### 1. Adding Customers to the Waitlist 74 | 75 | - Customers can join a waitlist on a product's single page. 76 | - The waitlist form captures the customer's email, saving it to the waitlist database table. 77 | - The plugin enqueues JavaScript only on out-of-stock single product pages, optimizing performance. 78 | 79 | ### 2. Notifying Customers 80 | 81 | When stock levels are updated (via WooCommerce®'s product update), the plugin will: 82 | 83 | - Check if the product's stock is now above zero. 84 | - Send a back-in-stock email notification to all users on that product's waitlist. 85 | - Log notifications and remove customers from the waitlist after notifying them. 86 | 87 | ### 3. Managing Waitlists and Analytics 88 | 89 | Admins can access the **Back In Stock** submenu under **WooCommerce®**. The dashboard provides insights, including: 90 | - **Most Wanted Products**: Products with the highest waitlist counts. 91 | - **Most Overdue Products**: Products that have been out of stock the longest. 92 | - **Most Signed-Up Products**: Top products based on waitlist sign-ups over time. 93 | - **Sign-Ups and Notifications**: Daily and monthly sign-up and notification statistics are tracked, with values stored for easy access via a helper class. 94 | 95 | ### 4. CSV Exporting 96 | 97 | Two CSV export options are available on the **Back In Stock** dashboard: 98 | - **Export Emails**: Exports all unique emails from the waitlist history table. 99 | - **Export Data**: Exports demand insights such as the most wanted, most overdue, and most signed-up products. 100 | 101 | ### Helper Functionality 102 | 103 | The plugin introduces a `BISN_Data_Helper` class to simplify database queries for various waitlist and notification statistics, such as: 104 | 105 | - `get_most_wanted_products()`: Retrieves the top 10 most wanted products based on waitlist count. 106 | - `get_most_overdue_products()`: Retrieves the top 10 products out of stock the longest. 107 | - `get_signups_today()`: Counts the sign-ups from today. 108 | - `get_sent_today()`: Counts notifications sent today. 109 | 110 | The helper class enables efficient data retrieval for a streamlined and modular plugin structure. 111 | 112 | ### Customizable Back In Stock Email 113 | 114 | The **BISN_Back_In_Stock_Email** class controls the back-in-stock email notifications, which are sent when products are restocked. Emails are triggered by the `bisn_send_back_in_stock_email` action, providing seamless integration into WooCommerce's email management system. -------------------------------------------------------------------------------- /assets/css/bisn-admin.css: -------------------------------------------------------------------------------- 1 | /* admin-bisn.css */ 2 | .tab-content { 3 | display: none; 4 | opacity: 0; 5 | } 6 | 7 | .bisn-dashboard-row { 8 | display: flex; 9 | gap: 20px; 10 | margin-top: 20px; 11 | } 12 | 13 | .bisn-dashboard-box { 14 | flex: 1; 15 | background: #f9f9f9; 16 | border: 1px solid #ddd; 17 | padding: 20px; 18 | border-radius: 8px; 19 | box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); 20 | text-align: center; 21 | } 22 | 23 | .bisn-dashboard-box h3 { 24 | font-size: 18px; 25 | color: #333; 26 | margin-bottom: 20px; 27 | margin-top: 0; 28 | } 29 | 30 | .bisn-grid { 31 | display: grid; 32 | grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); 33 | gap: 10px; 34 | margin-top: 15px; 35 | } 36 | 37 | .bisn-grid-item { 38 | background: #fff; 39 | padding: 10px; 40 | border-radius: 6px; 41 | box-shadow: 0 1px 4px rgba(0,0,0,0.1); 42 | display: flex; 43 | flex-direction: column; 44 | } 45 | 46 | .bisn-stat-number { 47 | font-size: 32px; 48 | font-weight: bold; 49 | color: #0073aa; 50 | margin: 12px 0; 51 | } 52 | 53 | .bisn-table { 54 | width: 100%; 55 | border-collapse: collapse; 56 | margin-top: 10px; 57 | } 58 | 59 | .bisn-table th, .bisn-table td { 60 | padding: 10px; 61 | border-bottom: 1px solid #ddd; 62 | text-align: left; 63 | } 64 | 65 | .bisn-table th { 66 | background-color: #f1f1f1; 67 | font-weight: bold; 68 | } 69 | 70 | .bisn-table th:nth-of-type(2), 71 | .bisn-table td:nth-of-type(2) { 72 | text-align: right; 73 | } 74 | 75 | @media (max-width: 768px) { 76 | .bisn-dashboard-row { 77 | flex-direction: column; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /assets/js/bisn-admin.js: -------------------------------------------------------------------------------- 1 | function showTab(event, tabId) { 2 | event.preventDefault(); 3 | 4 | var tabContent = document.getElementsByClassName("tab-content"); 5 | for (var i = 0; i < tabContent.length; i++) { 6 | tabContent[i].style.display = "none"; 7 | tabContent[i].style.opacity = "0"; 8 | } 9 | 10 | var tabs = document.getElementsByClassName("nav-tab"); 11 | for (var i = 0; i < tabs.length; i++) { 12 | tabs[i].classList.remove("nav-tab-active"); 13 | } 14 | 15 | var activeTab = document.getElementById(tabId); 16 | activeTab.style.display = "block"; 17 | activeTab.style.opacity = "1"; 18 | event.currentTarget.classList.add("nav-tab-active"); 19 | 20 | localStorage.setItem("activeTab", tabId); 21 | } 22 | 23 | document.addEventListener("DOMContentLoaded", function () { 24 | var activeTab = localStorage.getItem("activeTab") || "dashboard"; 25 | var tabContent = document.getElementById(activeTab); 26 | tabContent.style.display = "block"; 27 | tabContent.style.opacity = "1"; 28 | document.querySelector(`a[href="#${activeTab}"]`).click(); 29 | }); 30 | -------------------------------------------------------------------------------- /assets/js/bisn.js: -------------------------------------------------------------------------------- 1 | // js/bisn.js 2 | 3 | jQuery(document).ready(function($) { 4 | // Check if product is out of stock 5 | if ($('.stock.out-of-stock').length) { 6 | // Create the waitlist form HTML 7 | var waitlistForm = ` 8 |
Be notified when this product is back in stock!
10 | 11 | 12 |' . esc_html__( 'Product', 'bisn' ) . ' | '; 53 | $html .= '' . esc_html__( 'Signup Date', 'bisn' ) . ' | '; 54 | $html .= '' . esc_html__( 'Waiting Time', 'bisn' ) . ' | '; 55 | $html .= '' . esc_html__( 'Action', 'bisn' ) . ' | '; 56 | $html .= '
---|---|---|---|
' . esc_html( $product_name ) . ' | '; 74 | $html .= '' . esc_html( $signup_date ) . ' | '; 75 | $html .= '' . esc_html( $waiting_time ) . ' ' . esc_html__( 'ago', 'bisn' ) . ' | '; 76 | $html .= ''; 77 | $html .= ''; 81 | $html .= ' | '; 82 | $html .= '
' . esc_html__( 'You are not currently waitlisted for any products.', 'bisn' ) . '
'; 90 | } 91 | } 92 | 93 | /** 94 | * Register a custom endpoint for the WooCommerce account page. 95 | * 96 | * Adds the 'waitlist' endpoint to WooCommerce's rewrite rules. 97 | * 98 | * @since 1.0.0 99 | * @return void 100 | */ 101 | function bisn_add_account_waitlist_endpoint() { 102 | add_rewrite_endpoint( 'waitlist', EP_ROOT | EP_PAGES ); 103 | } 104 | add_action( 'init', 'bisn_add_account_waitlist_endpoint' ); 105 | 106 | /** 107 | * Add the 'waitlist' endpoint to WooCommerce query vars. 108 | * 109 | * @param array $vars Array of query vars. 110 | * 111 | * @since 1.0.0 112 | * @return array Modified array of query vars. 113 | */ 114 | function bisn_waitlist_query_vars( $vars ) { 115 | $vars[] = 'waitlist'; 116 | return $vars; 117 | } 118 | add_filter( 'query_vars', 'bisn_waitlist_query_vars' ); 119 | 120 | /** 121 | * Add the 'Waitlist' link to the WooCommerce My Account menu. 122 | * 123 | * @param array $items Array of menu items. 124 | * 125 | * @since 1.0.0 126 | * @return array Modified array of menu items. 127 | */ 128 | function bisn_add_waitlist_link_my_account( $items ) { 129 | $items['waitlist'] = esc_html__( 'Waitlist', 'bisn' ); 130 | return $items; 131 | } 132 | add_filter( 'woocommerce_account_menu_items', 'bisn_add_waitlist_link_my_account' ); 133 | 134 | /** 135 | * Display content for the custom 'waitlist' endpoint on the WooCommerce account page. 136 | * 137 | * @since 1.0.0 138 | * @return void 139 | */ 140 | function bisn_waitlist_endpoint_content() { 141 | bisn_account_waitlist_endpoint_content(); 142 | } 143 | add_action( 'woocommerce_account_waitlist_endpoint', 'bisn_waitlist_endpoint_content' ); 144 | 145 | /** 146 | * Handle the removal of a product from the waitlist. 147 | * 148 | * This function deletes the waitlist entry for the specified product when the user submits the form. 149 | * If the removal is successful, a success notice is displayed. 150 | * 151 | * @since 1.0.0 152 | * @return void 153 | */ 154 | function bisn_handle_waitlist_removal() { 155 | if ( isset( $_POST['bisn_remove_waitlist'], $_POST['product_id'] ) && is_user_logged_in() ) { 156 | $user_id = get_current_user_id(); 157 | $product_id = intval( $_POST['product_id'] ); 158 | 159 | global $wpdb; 160 | $table_name = $wpdb->prefix . 'bisn_waitlist'; 161 | 162 | $wpdb->delete( $table_name, [ 163 | 'user_id' => $user_id, 164 | 'product_id' => $product_id, 165 | ], [ '%d', '%d' ] ); 166 | 167 | wc_add_notice( esc_html__( 'You have been removed from the waitlist for this product.', 'bisn' ), 'success' ); 168 | 169 | // Redirect to avoid form resubmission on page refresh. 170 | wp_safe_redirect( wc_get_account_endpoint_url( 'waitlist' ) ); 171 | exit; 172 | } 173 | } 174 | add_action( 'template_redirect', 'bisn_handle_waitlist_removal' ); 175 | -------------------------------------------------------------------------------- /classes/class-bisn-back-in-stock-email.php: -------------------------------------------------------------------------------- 1 | 8 | * @license GPL-2.0+ http://www.gnu.org/licenses/gpl-2.0.txt 9 | * @link https://www.robertdevore.com 10 | * @since 1.0.0 11 | */ 12 | 13 | // If this file is called directly, abort. 14 | if ( ! defined( 'WPINC' ) ) { 15 | die; 16 | } 17 | 18 | class BISN_Back_In_Stock_Email extends WC_Email { 19 | 20 | public function __construct() { 21 | $this->id = 'bisn_back_in_stock'; 22 | $this->title = esc_html__( 'Back in Stock Notification', 'bisn' ); 23 | $this->description = esc_html__( 'Notification email sent to customers when a product is back in stock.', 'bisn' ); 24 | 25 | $this->heading = esc_html__( 'Your Product is Back in Stock!', 'bisn' ); 26 | $this->subject = esc_html__( '[{site_title}] Product Back in Stock: {product_name}', 'bisn' ); 27 | 28 | $this->template_html = 'emails/back-in-stock-notification.php'; 29 | $this->template_plain = 'emails/plain/back-in-stock-notification.php'; 30 | 31 | // Add template path to override WooCommerce templates. 32 | $this->template_base = plugin_dir_path( __FILE__ ) . 'templates/'; 33 | 34 | parent::__construct(); 35 | 36 | // Triggers this email when 'bisn_send_back_in_stock_email' is called. 37 | add_action( 'bisn_send_back_in_stock_email_notification', [ $this, 'trigger' ], 10, 2 ); 38 | } 39 | 40 | public function trigger( $email, $product ) { 41 | if ( ! $email || ! $product ) { 42 | return; 43 | } 44 | 45 | // Set the product ID for template use. 46 | $this->product_id = $product->get_id(); 47 | 48 | // Get the product name. 49 | $product_name = $product->get_name(); 50 | 51 | // Set the recipient's email address. 52 | $this->recipient = $email; 53 | 54 | // Replace the placeholder in the subject line. 55 | $this->subject = str_replace( '{product_name}', $product_name, $this->subject ); 56 | 57 | // Send the email. 58 | $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() ); 59 | } 60 | 61 | public function get_content_html() { 62 | return wc_get_template_html( $this->template_html, [ 63 | 'email_heading' => $this->get_heading(), 64 | 'product_id' => $this->product_id, 65 | 'sent_to_admin' => false, 66 | 'plain_text' => false, 67 | 'email' => $this, 68 | ], '', $this->template_base ); 69 | } 70 | 71 | public function get_content_plain() { 72 | return wc_get_template_html( $this->template_plain, [ 73 | 'email_heading' => $this->get_heading(), 74 | 'product_id' => $this->product_id, 75 | 'sent_to_admin' => false, 76 | 'plain_text' => true, 77 | 'email' => $this, 78 | ], '', $this->template_base ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /classes/class-bisn-waitlist-table.php: -------------------------------------------------------------------------------- 1 | 8 | * @license GPL-2.0+ http://www.gnu.org/licenses/gpl-2.0.txt 9 | * @link https://www.robertdevore.com 10 | * @since 1.0.0 11 | */ 12 | 13 | // If this file is called directly, abort. 14 | if ( ! defined( 'WPINC' ) ) { 15 | die; 16 | } 17 | 18 | if ( ! class_exists( 'WP_List_Table' ) ) { 19 | require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; 20 | } 21 | 22 | /** 23 | * Define the Back In Stock Waitlist Table. 24 | */ 25 | class BISN_Waitlist_Table extends WP_List_Table { 26 | 27 | public function __construct() { 28 | parent::__construct( [ 29 | 'singular' => esc_html__( 'Waitlist Entry', 'bisn' ), 30 | 'plural' => esc_html__( 'Waitlist Entries', 'bisn' ), 31 | 'ajax' => false 32 | ] ); 33 | } 34 | 35 | /** 36 | * Retrieve waitlist data from the database. 37 | * 38 | * @since 1.0.0 39 | * @return mixed 40 | */ 41 | public static function get_waitlist_data( $per_page = 20, $page_number = 1 ) { 42 | global $wpdb; 43 | 44 | $table_name = $wpdb->prefix . 'bisn_waitlist'; 45 | $sql = "SELECT * FROM $table_name"; 46 | 47 | if ( ! empty( $_REQUEST['orderby'] ) ) { 48 | $sql .= ' ORDER BY ' . esc_sql( $_REQUEST['orderby'] ); 49 | $sql .= ! empty( $_REQUEST['order'] ) ? ' ' . esc_sql( $_REQUEST['order'] ) : ' ASC'; 50 | } 51 | 52 | $sql .= " LIMIT $per_page"; 53 | $sql .= ' OFFSET ' . ( $page_number - 1 ) * $per_page; 54 | 55 | return $wpdb->get_results( $sql, 'ARRAY_A' ); 56 | } 57 | 58 | /** 59 | * Get total waitlist item count. 60 | * 61 | * @since 1.0.0 62 | * @return ?string 63 | */ 64 | public static function record_count() { 65 | global $wpdb; 66 | $table_name = $wpdb->prefix . 'bisn_waitlist'; 67 | return $wpdb->get_var( "SELECT COUNT(*) FROM $table_name" ); 68 | } 69 | 70 | /** 71 | * Define columns. 72 | * 73 | * @since 1.0.0 74 | * @return array 75 | */ 76 | public function get_columns() { 77 | return [ 78 | 'product_id' => esc_html__( 'Product', 'bisn' ), 79 | 'email' => esc_html__( 'Email', 'bisn' ), 80 | 'signed_up' => esc_html__( 'Signed Up', 'bisn' ), 81 | 'waiting' => esc_html__( 'Waiting', 'bisn' ), 82 | ]; 83 | } 84 | 85 | /** 86 | * Render the product_id column with a clickable link to the Edit screen. 87 | * 88 | * @since 1.0.0 89 | * @return string 90 | */ 91 | protected function column_product_id( $item ) { 92 | $product_id = absint( $item['product_id'] ); 93 | $title = get_the_title( $product_id ); 94 | $edit_link = get_edit_post_link( $product_id ); 95 | 96 | return sprintf( 97 | '%2$s', 98 | esc_url( $edit_link ), 99 | esc_html( $title ) 100 | ); 101 | } 102 | 103 | /** 104 | * Render the email column with a clickable link to the user profile (if associated). 105 | * 106 | * @since 1.0.0 107 | * @return string 108 | */ 109 | protected function column_email( $item ) { 110 | $user_id = absint( $item['user_id'] ); 111 | $email = esc_html( $item['email'] ); 112 | 113 | if ( $user_id ) { 114 | $user_edit_link = get_edit_user_link( $user_id ); 115 | return sprintf( '%2$s', esc_url( $user_edit_link ), $email ); 116 | } 117 | 118 | return $email; 119 | } 120 | 121 | /** 122 | * Render the signed_up column with a readable date and time based on WordPress timezone. 123 | * 124 | * @since 1.0.0 125 | * @return string 126 | */ 127 | protected function column_signed_up( $item ) { 128 | $timestamp = strtotime( $item['date_added'] ); 129 | return esc_html( date_i18n( 'F j, Y g:i A', $timestamp + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ); 130 | } 131 | 132 | /** 133 | * Render the waiting column showing time elapsed since sign-up, based on WordPress timezone. 134 | * 135 | * @since 1.0.0 136 | * @return string 137 | */ 138 | protected function column_waiting( $item ) { 139 | $signup_time = strtotime( $item['date_added'] ); 140 | $current_time = current_time( 'timestamp' ); 141 | $interval = abs( $current_time - $signup_time ); 142 | $waiting_time = ''; 143 | 144 | $days = floor( $interval / DAY_IN_SECONDS ); 145 | $hours = floor( ( $interval % DAY_IN_SECONDS ) / HOUR_IN_SECONDS ); 146 | $minutes = floor( ( $interval % HOUR_IN_SECONDS ) / MINUTE_IN_SECONDS ); 147 | 148 | if ( $days ) $waiting_time .= $days . ' days, '; 149 | if ( $hours ) $waiting_time .= $hours . ' hours, '; 150 | if ( $minutes ) $waiting_time .= $minutes . ' minutes'; 151 | 152 | return esc_html( rtrim( $waiting_time, ', ' ) ); 153 | } 154 | 155 | /** 156 | * Prepare the items for display in the table. 157 | * 158 | * @since 1.0.0 159 | * @return void 160 | */ 161 | public function prepare_items() { 162 | $this->_column_headers = [ 163 | $this->get_columns(), 164 | [], 165 | $this->get_sortable_columns() 166 | ]; 167 | 168 | $per_page = $this->get_items_per_page( 'waitlist_per_page', 20 ); 169 | $current_page = $this->get_pagenum(); 170 | $total_items = self::record_count(); 171 | 172 | $this->set_pagination_args( [ 173 | 'total_items' => $total_items, 174 | 'per_page' => $per_page, 175 | ] ); 176 | 177 | $this->items = self::get_waitlist_data( $per_page, $current_page ); 178 | } 179 | 180 | /** 181 | * Display sortable columns. 182 | * 183 | * @since 1.0.0 184 | * @return array 185 | */ 186 | public function get_sortable_columns() { 187 | return [ 188 | 'product_id' => [ 'product_id', true ], 189 | 'signed_up' => [ 'date_added', true ], 190 | ]; 191 | } 192 | 193 | /** 194 | * Default column rendering. 195 | * 196 | * @since 1.0.0 197 | * @return mixed 198 | */ 199 | protected function column_default( $item, $column_name ) { 200 | return $item[ $column_name ]; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /classes/templates/emails/back-in-stock-notification.php: -------------------------------------------------------------------------------- 1 | get_name(); 13 | $product_image_url = wp_get_attachment_url( $product->get_image_id() ); 14 | $product_price = $product->get_price_html(); 15 | $product_url = get_permalink( $product->get_id() ); 16 | } else { 17 | echo '' . esc_html__( 'We are sorry, but we could not retrieve the product details.', 'bisn' ) . '
'; 18 | return; 19 | } 20 | ?> 21 | 22 | 23 | 24 | 25 |31 | 32 |
33 | 34 |35 | 36 | 37 | 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /classes/templates/emails/plain/back-in-stock-notification.php: -------------------------------------------------------------------------------- 1 | get_name(); 9 | $product_price = $product->get_price(); 10 | $product_url = get_permalink( $product->get_id() ); 11 | 12 | echo sprintf( __( 'Great news! "%s" is now back in stock and available for purchase.', 'bisn' ), $product_name ) . "\n\n"; 13 | 14 | echo "Product: {$product_name}\n"; 15 | echo "Price: \${$product_price}\n"; 16 | echo "Link: {$product_url}\n\n"; 17 | 18 | echo "Shop Now: {$product_url}\n"; 19 | 20 | echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; 21 | 22 | do_action( 'woocommerce_email_footer_text', $email ); 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "robertdevore/wpcom-check": "^1.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "455764cd9b78270c5af3fb1bf25af96e", 8 | "packages": [ 9 | { 10 | "name": "robertdevore/wpcom-check", 11 | "version": "1.0.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/robertdevore/wpcom-check.git", 15 | "reference": "25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/robertdevore/wpcom-check/zipball/25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058", 20 | "reference": "25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=7.4" 25 | }, 26 | "type": "library", 27 | "extra": { 28 | "wordpress-plugin": true 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "RobertDevore\\WPComCheck\\": "src/" 33 | } 34 | }, 35 | "notification-url": "https://packagist.org/downloads/", 36 | "license": [ 37 | "MIT" 38 | ], 39 | "authors": [ 40 | { 41 | "name": "Robert DeVore", 42 | "email": "me@robertdevore.com", 43 | "homepage": "https://github.com/robertdevore", 44 | "role": "Developer" 45 | } 46 | ], 47 | "description": "A utility to handle WordPress.com-specific plugin compatibility and auto-deactivation.", 48 | "homepage": "https://github.com/robertdevore/wpcom-check", 49 | "keywords": [ 50 | "compatibility", 51 | "deactivation", 52 | "plugin", 53 | "wordpress", 54 | "wordpress.com" 55 | ], 56 | "support": { 57 | "issues": "https://github.com/robertdevore/wpcom-check/issues", 58 | "source": "https://github.com/robertdevore/wpcom-check/tree/1.0.1" 59 | }, 60 | "time": "2025-01-07T17:17:53+00:00" 61 | } 62 | ], 63 | "packages-dev": [], 64 | "aliases": [], 65 | "minimum-stability": "stable", 66 | "stability-flags": {}, 67 | "prefer-stable": false, 68 | "prefer-lowest": false, 69 | "platform": {}, 70 | "platform-dev": {}, 71 | "plugin-api-version": "2.6.0" 72 | } 73 | -------------------------------------------------------------------------------- /includes/helper-functions.php: -------------------------------------------------------------------------------- 1 | 8 | * @license GPL-2.0+ http://www.gnu.org/licenses/gpl-2.0.txt 9 | * @link https://www.robertdevore.com 10 | * @since 1.0.0 11 | */ 12 | 13 | // If this file is called directly, abort. 14 | if ( ! defined( 'WPINC' ) ) { 15 | die; 16 | } 17 | 18 | /** 19 | * Allowed HTML tags 20 | * 21 | * This function extends the wp_kses_allowed_html function to include 22 | * a handful of additional HTML fields that are used throughout 23 | * this plugin 24 | * 25 | * @since 1.0.0 26 | * @return array 27 | */ 28 | function bisn_allowed_tags() { 29 | $my_allowed = wp_kses_allowed_html( 'post' ); 30 | 31 | // Allow form element 32 | $my_allowed['form'] = [ 33 | 'method' => [], 34 | 'action' => [], 35 | 'class' => [], 36 | 'id' => [], 37 | ]; 38 | 39 | // Allow button element 40 | $my_allowed['button'] = [ 41 | 'type' => [], 42 | 'name' => [], 43 | 'class' => [], 44 | 'id' => [], 45 | 'value' => [], 46 | ]; 47 | 48 | // Allow input fields 49 | $my_allowed['input'] = [ 50 | 'class' => [], 51 | 'id' => [], 52 | 'name' => [], 53 | 'value' => [], 54 | 'type' => [], 55 | 'checked' => [], 56 | ]; 57 | 58 | // Allow select 59 | $my_allowed['select'] = [ 60 | 'class' => [], 61 | 'id' => [], 62 | 'name' => [], 63 | 'value' => [], 64 | 'type' => [], 65 | ]; 66 | 67 | // Allow select options 68 | $my_allowed['option'] = [ 69 | 'selected' => [], 70 | 'value' => [], 71 | ]; 72 | 73 | // Allow inline styles 74 | $my_allowed['style'] = [ 75 | 'types' => [], 76 | ]; 77 | 78 | // Allow iframe 79 | $my_allowed['iframe'] = [ 80 | 'src' => [], 81 | 'height' => [], 82 | 'width' => [], 83 | 'frameborder' => [], 84 | 'allowfullscreen' => [], 85 | ]; 86 | 87 | // Allow SVG elements 88 | $my_allowed['svg'] = [ 89 | 'xmlns' => [], 90 | 'width' => [], 91 | 'height' => [], 92 | 'viewBox' => [], 93 | 'stroke-width' => [], 94 | 'stroke' => [], 95 | 'fill' => [], 96 | 'stroke-linecap' => [], 97 | 'stroke-linejoin' => [], 98 | 'class' => [], 99 | ]; 100 | $my_allowed['path'] = [ 101 | 'd' => [], 102 | 'stroke' => [], 103 | 'fill' => [], 104 | ]; 105 | $my_allowed['line'] = [ 106 | 'x1' => [], 107 | 'y1' => [], 108 | 'x2' => [], 109 | 'y2' => [], 110 | 'stroke' => [], 111 | ]; 112 | 113 | return $my_allowed; 114 | } 115 | -------------------------------------------------------------------------------- /languages/bisn-af.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertdevore/back-in-stock-notifications/27c0946952055cb66ebd7ec53e0aa8374b969057/languages/bisn-af.mo -------------------------------------------------------------------------------- /languages/bisn-es_MX.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertdevore/back-in-stock-notifications/27c0946952055cb66ebd7ec53e0aa8374b969057/languages/bisn-es_MX.mo -------------------------------------------------------------------------------- /languages/bisn-fr_FR.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertdevore/back-in-stock-notifications/27c0946952055cb66ebd7ec53e0aa8374b969057/languages/bisn-fr_FR.mo -------------------------------------------------------------------------------- /languages/bisn-it_IT.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertdevore/back-in-stock-notifications/27c0946952055cb66ebd7ec53e0aa8374b969057/languages/bisn-it_IT.mo -------------------------------------------------------------------------------- /languages/bisn.pot: -------------------------------------------------------------------------------- 1 | #, fuzzy 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: Back In Stock Notifications for WooCommerce®\n" 5 | "POT-Creation-Date: 2025-02-21 19:56-0500\n" 6 | "PO-Revision-Date: 2025-02-21 19:56-0500\n" 7 | "Last-Translator: \n" 8 | "Language-Team: \n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 13 | "X-Generator: Poedit 3.5\n" 14 | "X-Poedit-Basepath: ..\n" 15 | "X-Poedit-Flags-xgettext: --add-comments=translators:\n" 16 | "X-Poedit-WPHeader: back-in-stock-notifications.php\n" 17 | "X-Poedit-SourceCharset: UTF-8\n" 18 | "X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;" 19 | "esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;" 20 | "_nx_noop:3c,1,2;__ngettext_noop:1,2\n" 21 | "X-Poedit-SearchPath-0: .\n" 22 | "X-Poedit-SearchPathExcluded-0: *.min.js\n" 23 | "X-Poedit-SearchPathExcluded-1: vendor\n" 24 | 25 | #: back-in-stock-notifications.php:78 26 | msgid "" 27 | "Back In Stock Notifications requires WooCommerce to be installed and active. " 28 | "Please activate WooCommerce and try again." 29 | msgstr "" 30 | 31 | #: back-in-stock-notifications.php:79 32 | msgid "Plugin Activation Error" 33 | msgstr "" 34 | 35 | #: back-in-stock-notifications.php:94 36 | msgid "" 37 | "Back In Stock Notifications requires WooCommerce to be active. Please " 38 | "activate WooCommerce to use this plugin." 39 | msgstr "" 40 | 41 | #: back-in-stock-notifications.php:268 42 | msgid "Invalid email address." 43 | msgstr "" 44 | 45 | #: back-in-stock-notifications.php:294 46 | msgid "You have been added to the waitlist." 47 | msgstr "" 48 | 49 | #: back-in-stock-notifications.php:363 50 | msgid "Back In Stock Waitlist" 51 | msgstr "" 52 | 53 | #: back-in-stock-notifications.php:364 54 | msgid "Back In Stock" 55 | msgstr "" 56 | 57 | #: back-in-stock-notifications.php:392 58 | msgid "Back In Stock Notifications" 59 | msgstr "" 60 | 61 | #: back-in-stock-notifications.php:398 62 | msgid "Export Emails" 63 | msgstr "" 64 | 65 | #: back-in-stock-notifications.php:402 66 | msgid "Export Data" 67 | msgstr "" 68 | 69 | #: back-in-stock-notifications.php:408 70 | msgid "Dashboard" 71 | msgstr "" 72 | 73 | #: back-in-stock-notifications.php:409 bisn-woocommerce-my-account.php:129 74 | msgid "Waitlist" 75 | msgstr "" 76 | 77 | #: back-in-stock-notifications.php:417 78 | msgid "Notifications" 79 | msgstr "" 80 | 81 | #: back-in-stock-notifications.php:421 82 | msgid "Sent Last Month" 83 | msgstr "" 84 | 85 | #: back-in-stock-notifications.php:425 86 | msgid "Sent Today" 87 | msgstr "" 88 | 89 | #: back-in-stock-notifications.php:429 90 | msgid "Queued" 91 | msgstr "" 92 | 93 | #: back-in-stock-notifications.php:434 94 | msgid "Sign-ups" 95 | msgstr "" 96 | 97 | #: back-in-stock-notifications.php:438 98 | msgid "Sign-ups Last Month" 99 | msgstr "" 100 | 101 | #: back-in-stock-notifications.php:442 102 | msgid "Signed up Today" 103 | msgstr "" 104 | 105 | #: back-in-stock-notifications.php:450 106 | msgid "Most Wanted" 107 | msgstr "" 108 | 109 | #: back-in-stock-notifications.php:454 back-in-stock-notifications.php:476 110 | #: back-in-stock-notifications.php:498 bisn-woocommerce-my-account.php:52 111 | #: classes/class-bisn-waitlist-table.php:78 112 | msgid "Product" 113 | msgstr "" 114 | 115 | #: back-in-stock-notifications.php:455 back-in-stock-notifications.php:499 116 | msgid "Customers" 117 | msgstr "" 118 | 119 | #: back-in-stock-notifications.php:472 120 | msgid "Most Overdue" 121 | msgstr "" 122 | 123 | #: back-in-stock-notifications.php:477 124 | msgid "Days Waiting" 125 | msgstr "" 126 | 127 | #: back-in-stock-notifications.php:494 128 | msgid "Most Signed-up" 129 | msgstr "" 130 | 131 | #: back-in-stock-notifications.php:554 132 | msgid "Unauthorized request." 133 | msgstr "" 134 | 135 | #: back-in-stock-notifications.php:597 136 | msgid "You do not have permission to access this page." 137 | msgstr "" 138 | 139 | #: bisn-woocommerce-my-account.php:49 140 | msgid "Your Waitlisted Products" 141 | msgstr "" 142 | 143 | #: bisn-woocommerce-my-account.php:53 144 | msgid "Signup Date" 145 | msgstr "" 146 | 147 | #: bisn-woocommerce-my-account.php:54 148 | msgid "Waiting Time" 149 | msgstr "" 150 | 151 | #: bisn-woocommerce-my-account.php:55 152 | msgid "Action" 153 | msgstr "" 154 | 155 | #: bisn-woocommerce-my-account.php:75 156 | msgid "ago" 157 | msgstr "" 158 | 159 | #: bisn-woocommerce-my-account.php:79 160 | msgid "Remove" 161 | msgstr "" 162 | 163 | #: bisn-woocommerce-my-account.php:89 164 | msgid "You are not currently waitlisted for any products." 165 | msgstr "" 166 | 167 | #: bisn-woocommerce-my-account.php:167 168 | msgid "You have been removed from the waitlist for this product." 169 | msgstr "" 170 | 171 | #: classes/class-bisn-back-in-stock-email.php:22 172 | msgid "Back in Stock Notification" 173 | msgstr "" 174 | 175 | #: classes/class-bisn-back-in-stock-email.php:23 176 | msgid "Notification email sent to customers when a product is back in stock." 177 | msgstr "" 178 | 179 | #: classes/class-bisn-back-in-stock-email.php:25 180 | msgid "Your Product is Back in Stock!" 181 | msgstr "" 182 | 183 | #: classes/class-bisn-back-in-stock-email.php:26 184 | msgid "[{site_title}] Product Back in Stock: {product_name}" 185 | msgstr "" 186 | 187 | #: classes/class-bisn-waitlist-table.php:29 188 | msgid "Waitlist Entry" 189 | msgstr "" 190 | 191 | #: classes/class-bisn-waitlist-table.php:30 192 | msgid "Waitlist Entries" 193 | msgstr "" 194 | 195 | #: classes/class-bisn-waitlist-table.php:79 196 | msgid "Email" 197 | msgstr "" 198 | 199 | #: classes/class-bisn-waitlist-table.php:80 200 | msgid "Signed Up" 201 | msgstr "" 202 | 203 | #: classes/class-bisn-waitlist-table.php:81 204 | msgid "Waiting" 205 | msgstr "" 206 | 207 | #: classes/templates/emails/back-in-stock-notification.php:17 208 | msgid "We are sorry, but we could not retrieve the product details." 209 | msgstr "" 210 | 211 | #: classes/templates/emails/back-in-stock-notification.php:22 212 | #: classes/templates/emails/plain/back-in-stock-notification.php:12 213 | #, php-format 214 | msgid "Great news! \"%s\" is now back in stock and available for purchase." 215 | msgstr "" 216 | 217 | #: classes/templates/emails/back-in-stock-notification.php:36 218 | msgid "Shop Now" 219 | msgstr "" 220 | 221 | #. Plugin Name of the plugin/theme 222 | msgid "Back In Stock Notifications for WooCommerce®" 223 | msgstr "" 224 | 225 | #. Plugin URI of the plugin/theme 226 | msgid "https://github.com/robertdevore/back-in-stock-notifications/" 227 | msgstr "" 228 | 229 | #. Description of the plugin/theme 230 | msgid "" 231 | "Automatically notify customers when their favorite products are restocked, " 232 | "and track what products are most in demand." 233 | msgstr "" 234 | 235 | #. Author of the plugin/theme 236 | msgid "Robert DeVore" 237 | msgstr "" 238 | 239 | #. Author URI of the plugin/theme 240 | msgid "https://robertdevore.com/" 241 | msgstr "" 242 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/robertdevore/wpcom-check/src'), 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 35 | 36 | return $loader; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'RobertDevore\\WPComCheck\\' => 24, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'RobertDevore\\WPComCheck\\' => 18 | array ( 19 | 0 => __DIR__ . '/..' . '/robertdevore/wpcom-check/src', 20 | ), 21 | ); 22 | 23 | public static $classMap = array ( 24 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 25 | ); 26 | 27 | public static function getInitializer(ClassLoader $loader) 28 | { 29 | return \Closure::bind(function () use ($loader) { 30 | $loader->prefixLengthsPsr4 = ComposerStaticInitca9049066c1270338606f5739f59d5ab::$prefixLengthsPsr4; 31 | $loader->prefixDirsPsr4 = ComposerStaticInitca9049066c1270338606f5739f59d5ab::$prefixDirsPsr4; 32 | $loader->classMap = ComposerStaticInitca9049066c1270338606f5739f59d5ab::$classMap; 33 | 34 | }, null, ClassLoader::class); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "name": "robertdevore/wpcom-check", 5 | "version": "1.0.1", 6 | "version_normalized": "1.0.1.0", 7 | "source": { 8 | "type": "git", 9 | "url": "https://github.com/robertdevore/wpcom-check.git", 10 | "reference": "25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058" 11 | }, 12 | "dist": { 13 | "type": "zip", 14 | "url": "https://api.github.com/repos/robertdevore/wpcom-check/zipball/25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058", 15 | "reference": "25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058", 16 | "shasum": "" 17 | }, 18 | "require": { 19 | "php": ">=7.4" 20 | }, 21 | "time": "2025-01-07T17:17:53+00:00", 22 | "type": "library", 23 | "extra": { 24 | "wordpress-plugin": true 25 | }, 26 | "installation-source": "dist", 27 | "autoload": { 28 | "psr-4": { 29 | "RobertDevore\\WPComCheck\\": "src/" 30 | } 31 | }, 32 | "notification-url": "https://packagist.org/downloads/", 33 | "license": [ 34 | "MIT" 35 | ], 36 | "authors": [ 37 | { 38 | "name": "Robert DeVore", 39 | "email": "me@robertdevore.com", 40 | "homepage": "https://github.com/robertdevore", 41 | "role": "Developer" 42 | } 43 | ], 44 | "description": "A utility to handle WordPress.com-specific plugin compatibility and auto-deactivation.", 45 | "homepage": "https://github.com/robertdevore/wpcom-check", 46 | "keywords": [ 47 | "compatibility", 48 | "deactivation", 49 | "plugin", 50 | "wordpress", 51 | "wordpress.com" 52 | ], 53 | "support": { 54 | "issues": "https://github.com/robertdevore/wpcom-check/issues", 55 | "source": "https://github.com/robertdevore/wpcom-check/tree/1.0.1" 56 | }, 57 | "install-path": "../robertdevore/wpcom-check" 58 | } 59 | ], 60 | "dev": true, 61 | "dev-package-names": [] 62 | } 63 | -------------------------------------------------------------------------------- /vendor/composer/installed.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'name' => '__root__', 4 | 'pretty_version' => 'dev-develop', 5 | 'version' => 'dev-develop', 6 | 'reference' => '7d23ab5ef14c1d85ddac3fe6453202f44483b8cf', 7 | 'type' => 'library', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => true, 11 | ), 12 | 'versions' => array( 13 | '__root__' => array( 14 | 'pretty_version' => 'dev-develop', 15 | 'version' => 'dev-develop', 16 | 'reference' => '7d23ab5ef14c1d85ddac3fe6453202f44483b8cf', 17 | 'type' => 'library', 18 | 'install_path' => __DIR__ . '/../../', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | 'robertdevore/wpcom-check' => array( 23 | 'pretty_version' => '1.0.1', 24 | 'version' => '1.0.1.0', 25 | 'reference' => '25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058', 26 | 'type' => 'library', 27 | 'install_path' => __DIR__ . '/../robertdevore/wpcom-check', 28 | 'aliases' => array(), 29 | 'dev_requirement' => false, 30 | ), 31 | ), 32 | ); 33 | -------------------------------------------------------------------------------- /vendor/composer/platform_check.php: -------------------------------------------------------------------------------- 1 | = 70400)) { 8 | $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; 9 | } 10 | 11 | if ($issues) { 12 | if (!headers_sent()) { 13 | header('HTTP/1.1 500 Internal Server Error'); 14 | } 15 | if (!ini_get('display_errors')) { 16 | if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { 17 | fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); 18 | } elseif (!headers_sent()) { 19 | echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; 20 | } 21 | } 22 | trigger_error( 23 | 'Composer detected issues in your platform: ' . implode(' ', $issues), 24 | E_USER_ERROR 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5/PucFactory.php: -------------------------------------------------------------------------------- 1 | rootDir = dirname(__FILE__) . '/'; 18 | 19 | $namespaceWithSlash = __NAMESPACE__ . '\\'; 20 | $this->prefix = $namespaceWithSlash; 21 | 22 | $this->libraryDir = $this->rootDir . '../..'; 23 | if ( !self::isPhar() ) { 24 | $this->libraryDir = realpath($this->libraryDir); 25 | } 26 | $this->libraryDir = $this->libraryDir . '/'; 27 | 28 | //Usually, dependencies like Parsedown are in the global namespace, 29 | //but if someone adds a custom namespace to the entire library, they 30 | //will be in the same namespace as this class. 31 | $isCustomNamespace = ( 32 | substr($namespaceWithSlash, 0, strlen(self::DEFAULT_NS_PREFIX)) !== self::DEFAULT_NS_PREFIX 33 | ); 34 | $libraryPrefix = $isCustomNamespace ? $namespaceWithSlash : ''; 35 | 36 | $this->staticMap = array( 37 | $libraryPrefix . 'PucReadmeParser' => 'vendor/PucReadmeParser.php', 38 | $libraryPrefix . 'Parsedown' => 'vendor/Parsedown.php', 39 | ); 40 | 41 | //Add the generic, major-version-only factory class to the static map. 42 | $versionSeparatorPos = strrpos(__NAMESPACE__, '\\v'); 43 | if ( $versionSeparatorPos !== false ) { 44 | $versionSegment = substr(__NAMESPACE__, $versionSeparatorPos + 1); 45 | $pointPos = strpos($versionSegment, 'p'); 46 | if ( ($pointPos !== false) && ($pointPos > 1) ) { 47 | $majorVersionSegment = substr($versionSegment, 0, $pointPos); 48 | $majorVersionNs = __NAMESPACE__ . '\\' . $majorVersionSegment; 49 | $this->staticMap[$majorVersionNs . '\\PucFactory'] = 50 | 'Puc/' . $majorVersionSegment . '/Factory.php'; 51 | } 52 | } 53 | 54 | spl_autoload_register(array($this, 'autoload')); 55 | } 56 | 57 | /** 58 | * Determine if this file is running as part of a Phar archive. 59 | * 60 | * @return bool 61 | */ 62 | private static function isPhar() { 63 | //Check if the current file path starts with "phar://". 64 | static $pharProtocol = 'phar://'; 65 | return (substr(__FILE__, 0, strlen($pharProtocol)) === $pharProtocol); 66 | } 67 | 68 | public function autoload($className) { 69 | if ( isset($this->staticMap[$className]) && file_exists($this->libraryDir . $this->staticMap[$className]) ) { 70 | include($this->libraryDir . $this->staticMap[$className]); 71 | return; 72 | } 73 | 74 | if ( strpos($className, $this->prefix) === 0 ) { 75 | $path = substr($className, strlen($this->prefix)); 76 | $path = str_replace(array('_', '\\'), '/', $path); 77 | $path = $this->rootDir . $path . '.php'; 78 | 79 | if ( file_exists($path) ) { 80 | include $path; 81 | } 82 | } 83 | } 84 | } 85 | 86 | endif; 87 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/DebugBar/Panel.php: -------------------------------------------------------------------------------- 1 | '; 13 | 14 | public function __construct($updateChecker) { 15 | $this->updateChecker = $updateChecker; 16 | $title = sprintf( 17 | ' ', 18 | esc_attr($this->updateChecker->getUniqueName('uid')), 19 | $this->updateChecker->slug 20 | ); 21 | parent::__construct($title); 22 | } 23 | 24 | public function render() { 25 | printf( 26 | ' '; 38 | } 39 | 40 | private function displayConfiguration() { 41 | echo '' . htmlentities(print_r($value, true)) . ''; 166 | } else if ($value === null) { 167 | $value = '
null
';
168 | }
169 | printf(
170 | '', esc_html(print_r($info, true)), ''; 33 | } else { 34 | echo 'Failed to retrieve plugin info from the metadata URL.'; 35 | } 36 | exit; 37 | } 38 | } 39 | 40 | endif; 41 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/DebugBar/PluginPanel.php: -------------------------------------------------------------------------------- 1 | row('Plugin file', htmlentities($this->updateChecker->pluginFile)); 16 | parent::displayConfigHeader(); 17 | } 18 | 19 | protected function getMetadataButton() { 20 | $requestInfoButton = ''; 21 | if ( function_exists('get_submit_button') ) { 22 | $requestInfoButton = get_submit_button( 23 | 'Request Info', 24 | 'secondary', 25 | 'puc-request-info-button', 26 | false, 27 | array('id' => $this->updateChecker->getUniqueName('request-info-button')) 28 | ); 29 | } 30 | return $requestInfoButton; 31 | } 32 | 33 | protected function getUpdateFields() { 34 | return array_merge( 35 | parent::getUpdateFields(), 36 | array('homepage', 'upgrade_notice', 'tested',) 37 | ); 38 | } 39 | } 40 | 41 | endif; 42 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/DebugBar/ThemePanel.php: -------------------------------------------------------------------------------- 1 | row('Theme directory', htmlentities($this->updateChecker->directoryName)); 17 | parent::displayConfigHeader(); 18 | } 19 | 20 | protected function getUpdateFields() { 21 | return array_merge(parent::getUpdateFields(), array('details_url')); 22 | } 23 | } 24 | 25 | endif; 26 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/InstalledPackage.php: -------------------------------------------------------------------------------- 1 | updateChecker = $updateChecker; 20 | } 21 | 22 | /** 23 | * Get the currently installed version of the plugin or theme. 24 | * 25 | * @return string|null Version number. 26 | */ 27 | abstract public function getInstalledVersion(); 28 | 29 | /** 30 | * Get the full path of the plugin or theme directory (without a trailing slash). 31 | * 32 | * @return string 33 | */ 34 | abstract public function getAbsoluteDirectoryPath(); 35 | 36 | /** 37 | * Check whether a regular file exists in the package's directory. 38 | * 39 | * @param string $relativeFileName File name relative to the package directory. 40 | * @return bool 41 | */ 42 | public function fileExists($relativeFileName) { 43 | return is_file( 44 | $this->getAbsoluteDirectoryPath() 45 | . DIRECTORY_SEPARATOR 46 | . ltrim($relativeFileName, '/\\') 47 | ); 48 | } 49 | 50 | /* ------------------------------------------------------------------- 51 | * File header parsing 52 | * ------------------------------------------------------------------- 53 | */ 54 | 55 | /** 56 | * Parse plugin or theme metadata from the header comment. 57 | * 58 | * This is basically a simplified version of the get_file_data() function from /wp-includes/functions.php. 59 | * It's intended as a utility for subclasses that detect updates by parsing files in a VCS. 60 | * 61 | * @param string|null $content File contents. 62 | * @return string[] 63 | */ 64 | public function getFileHeader($content) { 65 | $content = (string)$content; 66 | 67 | //WordPress only looks at the first 8 KiB of the file, so we do the same. 68 | $content = substr($content, 0, 8192); 69 | //Normalize line endings. 70 | $content = str_replace("\r", "\n", $content); 71 | 72 | $headers = $this->getHeaderNames(); 73 | $results = array(); 74 | foreach ($headers as $field => $name) { 75 | $success = preg_match('/^[ \t\/*#@]*' . preg_quote($name, '/') . ':(.*)$/mi', $content, $matches); 76 | 77 | if ( ($success === 1) && $matches[1] ) { 78 | $value = $matches[1]; 79 | if ( function_exists('_cleanup_header_comment') ) { 80 | $value = _cleanup_header_comment($value); 81 | } 82 | $results[$field] = $value; 83 | } else { 84 | $results[$field] = ''; 85 | } 86 | } 87 | 88 | return $results; 89 | } 90 | 91 | /** 92 | * @return array Format: ['HeaderKey' => 'Header Name'] 93 | */ 94 | abstract protected function getHeaderNames(); 95 | 96 | /** 97 | * Get the value of a specific plugin or theme header. 98 | * 99 | * @param string $headerName 100 | * @return string Either the value of the header, or an empty string if the header doesn't exist. 101 | */ 102 | abstract public function getHeaderValue($headerName); 103 | 104 | } 105 | endif; 106 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Metadata.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | protected $extraProperties = array(); 24 | 25 | /** 26 | * Create an instance of this class from a JSON document. 27 | * 28 | * @abstract 29 | * @param string $json 30 | * @return self 31 | */ 32 | public static function fromJson($json) { 33 | throw new LogicException('The ' . __METHOD__ . ' method must be implemented by subclasses'); 34 | } 35 | 36 | /** 37 | * @param string $json 38 | * @param self $target 39 | * @return bool 40 | */ 41 | protected static function createFromJson($json, $target) { 42 | /** @var \StdClass $apiResponse */ 43 | $apiResponse = json_decode($json); 44 | if ( empty($apiResponse) || !is_object($apiResponse) ){ 45 | $errorMessage = "Failed to parse update metadata. Try validating your .json file with https://jsonlint.com/"; 46 | do_action('puc_api_error', new WP_Error('puc-invalid-json', $errorMessage)); 47 | //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- For plugin developers. 48 | trigger_error(esc_html($errorMessage), E_USER_NOTICE); 49 | return false; 50 | } 51 | 52 | $valid = $target->validateMetadata($apiResponse); 53 | if ( is_wp_error($valid) ){ 54 | do_action('puc_api_error', $valid); 55 | //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- For plugin developers. 56 | trigger_error(esc_html($valid->get_error_message()), E_USER_NOTICE); 57 | return false; 58 | } 59 | 60 | foreach(get_object_vars($apiResponse) as $key => $value){ 61 | $target->$key = $value; 62 | } 63 | 64 | return true; 65 | } 66 | 67 | /** 68 | * No validation by default! Subclasses should check that the required fields are present. 69 | * 70 | * @param \StdClass $apiResponse 71 | * @return bool|\WP_Error 72 | */ 73 | protected function validateMetadata($apiResponse) { 74 | return true; 75 | } 76 | 77 | /** 78 | * Create a new instance by copying the necessary fields from another object. 79 | * 80 | * @abstract 81 | * @param \StdClass|self $object The source object. 82 | * @return self The new copy. 83 | */ 84 | public static function fromObject($object) { 85 | throw new LogicException('The ' . __METHOD__ . ' method must be implemented by subclasses'); 86 | } 87 | 88 | /** 89 | * Create an instance of StdClass that can later be converted back to an 90 | * update or info container. Useful for serialization and caching, as it 91 | * avoids the "incomplete object" problem if the cached value is loaded 92 | * before this class. 93 | * 94 | * @return \StdClass 95 | */ 96 | public function toStdClass() { 97 | $object = new stdClass(); 98 | $this->copyFields($this, $object); 99 | return $object; 100 | } 101 | 102 | /** 103 | * Transform the metadata into the format used by WordPress core. 104 | * 105 | * @return object 106 | */ 107 | abstract public function toWpFormat(); 108 | 109 | /** 110 | * Copy known fields from one object to another. 111 | * 112 | * @param \StdClass|self $from 113 | * @param \StdClass|self $to 114 | */ 115 | protected function copyFields($from, $to) { 116 | $fields = $this->getFieldNames(); 117 | 118 | if ( property_exists($from, 'slug') && !empty($from->slug) ) { 119 | //Let plugins add extra fields without having to create subclasses. 120 | $fields = apply_filters($this->getPrefixedFilter('retain_fields') . '-' . $from->slug, $fields); 121 | } 122 | 123 | foreach ($fields as $field) { 124 | if ( property_exists($from, $field) ) { 125 | $to->$field = $from->$field; 126 | } 127 | } 128 | } 129 | 130 | /** 131 | * @return string[] 132 | */ 133 | protected function getFieldNames() { 134 | return array(); 135 | } 136 | 137 | /** 138 | * @param string $tag 139 | * @return string 140 | */ 141 | protected function getPrefixedFilter($tag) { 142 | return 'puc_' . $tag; 143 | } 144 | 145 | public function __set($name, $value) { 146 | $this->extraProperties[$name] = $value; 147 | } 148 | 149 | public function __get($name) { 150 | return isset($this->extraProperties[$name]) ? $this->extraProperties[$name] : null; 151 | } 152 | 153 | public function __isset($name) { 154 | return isset($this->extraProperties[$name]); 155 | } 156 | 157 | public function __unset($name) { 158 | unset($this->extraProperties[$name]); 159 | } 160 | } 161 | 162 | endif; 163 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/OAuthSignature.php: -------------------------------------------------------------------------------- 1 | consumerKey = $consumerKey; 15 | $this->consumerSecret = $consumerSecret; 16 | } 17 | 18 | /** 19 | * Sign a URL using OAuth 1.0. 20 | * 21 | * @param string $url The URL to be signed. It may contain query parameters. 22 | * @param string $method HTTP method such as "GET", "POST" and so on. 23 | * @return string The signed URL. 24 | */ 25 | public function sign($url, $method = 'GET') { 26 | $parameters = array(); 27 | 28 | //Parse query parameters. 29 | $query = wp_parse_url($url, PHP_URL_QUERY); 30 | if ( !empty($query) ) { 31 | parse_str($query, $parsedParams); 32 | if ( is_array($parsedParams) ) { 33 | $parameters = $parsedParams; 34 | } 35 | //Remove the query string from the URL. We'll replace it later. 36 | $url = substr($url, 0, strpos($url, '?')); 37 | } 38 | 39 | $parameters = array_merge( 40 | $parameters, 41 | array( 42 | 'oauth_consumer_key' => $this->consumerKey, 43 | 'oauth_nonce' => $this->nonce(), 44 | 'oauth_signature_method' => 'HMAC-SHA1', 45 | 'oauth_timestamp' => time(), 46 | 'oauth_version' => '1.0', 47 | ) 48 | ); 49 | unset($parameters['oauth_signature']); 50 | 51 | //Parameters must be sorted alphabetically before signing. 52 | ksort($parameters); 53 | 54 | //The most complicated part of the request - generating the signature. 55 | //The string to sign contains the HTTP method, the URL path, and all of 56 | //our query parameters. Everything is URL encoded. Then we concatenate 57 | //them with ampersands into a single string to hash. 58 | $encodedVerb = urlencode($method); 59 | $encodedUrl = urlencode($url); 60 | $encodedParams = urlencode(http_build_query($parameters, '', '&')); 61 | 62 | $stringToSign = $encodedVerb . '&' . $encodedUrl . '&' . $encodedParams; 63 | 64 | //Since we only have one OAuth token (the consumer secret) we only have 65 | //to use it as our HMAC key. However, we still have to append an & to it 66 | //as if we were using it with additional tokens. 67 | $secret = urlencode($this->consumerSecret) . '&'; 68 | 69 | //The signature is a hash of the consumer key and the base string. Note 70 | //that we have to get the raw output from hash_hmac and base64 encode 71 | //the binary data result. 72 | $parameters['oauth_signature'] = base64_encode(hash_hmac('sha1', $stringToSign, $secret, true)); 73 | 74 | return ($url . '?' . http_build_query($parameters)); 75 | } 76 | 77 | /** 78 | * Generate a random nonce. 79 | * 80 | * @return string 81 | */ 82 | private function nonce() { 83 | $mt = microtime(); 84 | 85 | $rand = null; 86 | if ( is_callable('random_bytes') ) { 87 | try { 88 | $rand = random_bytes(16); 89 | } catch (\Exception $ex) { 90 | //Fall back to mt_rand (below). 91 | } 92 | } 93 | if ( $rand === null ) { 94 | //phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand 95 | $rand = function_exists('wp_rand') ? wp_rand() : mt_rand(); 96 | } 97 | 98 | return md5($mt . '_' . $rand); 99 | } 100 | } 101 | 102 | endif; 103 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Plugin/Package.php: -------------------------------------------------------------------------------- 1 | pluginAbsolutePath = $pluginAbsolutePath; 32 | $this->pluginFile = plugin_basename($this->pluginAbsolutePath); 33 | 34 | parent::__construct($updateChecker); 35 | 36 | //Clear the version number cache when something - anything - is upgraded or WP clears the update cache. 37 | add_filter('upgrader_post_install', array($this, 'clearCachedVersion')); 38 | add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion')); 39 | } 40 | 41 | public function getInstalledVersion() { 42 | if ( isset($this->cachedInstalledVersion) ) { 43 | return $this->cachedInstalledVersion; 44 | } 45 | 46 | $pluginHeader = $this->getPluginHeader(); 47 | if ( isset($pluginHeader['Version']) ) { 48 | $this->cachedInstalledVersion = $pluginHeader['Version']; 49 | return $pluginHeader['Version']; 50 | } else { 51 | //This can happen if the filename points to something that is not a plugin. 52 | $this->updateChecker->triggerError( 53 | sprintf( 54 | "Cannot read the Version header for '%s'. The filename is incorrect or is not a plugin.", 55 | $this->updateChecker->pluginFile 56 | ), 57 | E_USER_WARNING 58 | ); 59 | return null; 60 | } 61 | } 62 | 63 | /** 64 | * Clear the cached plugin version. This method can be set up as a filter (hook) and will 65 | * return the filter argument unmodified. 66 | * 67 | * @param mixed $filterArgument 68 | * @return mixed 69 | */ 70 | public function clearCachedVersion($filterArgument = null) { 71 | $this->cachedInstalledVersion = null; 72 | return $filterArgument; 73 | } 74 | 75 | public function getAbsoluteDirectoryPath() { 76 | return dirname($this->pluginAbsolutePath); 77 | } 78 | 79 | /** 80 | * Get the value of a specific plugin or theme header. 81 | * 82 | * @param string $headerName 83 | * @param string $defaultValue 84 | * @return string Either the value of the header, or $defaultValue if the header doesn't exist or is empty. 85 | */ 86 | public function getHeaderValue($headerName, $defaultValue = '') { 87 | $headers = $this->getPluginHeader(); 88 | if ( isset($headers[$headerName]) && ($headers[$headerName] !== '') ) { 89 | return $headers[$headerName]; 90 | } 91 | return $defaultValue; 92 | } 93 | 94 | protected function getHeaderNames() { 95 | return array( 96 | 'Name' => 'Plugin Name', 97 | 'PluginURI' => 'Plugin URI', 98 | 'Version' => 'Version', 99 | 'Description' => 'Description', 100 | 'Author' => 'Author', 101 | 'AuthorURI' => 'Author URI', 102 | 'TextDomain' => 'Text Domain', 103 | 'DomainPath' => 'Domain Path', 104 | 'Network' => 'Network', 105 | 106 | //The newest WordPress version that this plugin requires or has been tested with. 107 | //We support several different formats for compatibility with other libraries. 108 | 'Tested WP' => 'Tested WP', 109 | 'Requires WP' => 'Requires WP', 110 | 'Tested up to' => 'Tested up to', 111 | 'Requires at least' => 'Requires at least', 112 | ); 113 | } 114 | 115 | /** 116 | * Get the translated plugin title. 117 | * 118 | * @return string 119 | */ 120 | public function getPluginTitle() { 121 | $title = ''; 122 | $header = $this->getPluginHeader(); 123 | if ( $header && !empty($header['Name']) && isset($header['TextDomain']) ) { 124 | $title = translate($header['Name'], $header['TextDomain']); 125 | } 126 | return $title; 127 | } 128 | 129 | /** 130 | * Get plugin's metadata from its file header. 131 | * 132 | * @return array 133 | */ 134 | public function getPluginHeader() { 135 | if ( !is_file($this->pluginAbsolutePath) ) { 136 | //This can happen if the plugin filename is wrong. 137 | $this->updateChecker->triggerError( 138 | sprintf( 139 | "Can't to read the plugin header for '%s'. The file does not exist.", 140 | $this->updateChecker->pluginFile 141 | ), 142 | E_USER_WARNING 143 | ); 144 | return array(); 145 | } 146 | 147 | if ( !function_exists('get_plugin_data') ) { 148 | require_once(ABSPATH . '/wp-admin/includes/plugin.php'); 149 | } 150 | return get_plugin_data($this->pluginAbsolutePath, false, false); 151 | } 152 | 153 | public function removeHooks() { 154 | remove_filter('upgrader_post_install', array($this, 'clearCachedVersion')); 155 | remove_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion')); 156 | } 157 | 158 | /** 159 | * Check if the plugin file is inside the mu-plugins directory. 160 | * 161 | * @return bool 162 | */ 163 | public function isMuPlugin() { 164 | static $cachedResult = null; 165 | 166 | if ( $cachedResult === null ) { 167 | if ( !defined('WPMU_PLUGIN_DIR') || !is_string(WPMU_PLUGIN_DIR) ) { 168 | $cachedResult = false; 169 | return $cachedResult; 170 | } 171 | 172 | //Convert both paths to the canonical form before comparison. 173 | $muPluginDir = realpath(WPMU_PLUGIN_DIR); 174 | $pluginPath = realpath($this->pluginAbsolutePath); 175 | //If realpath() fails, just normalize the syntax instead. 176 | if (($muPluginDir === false) || ($pluginPath === false)) { 177 | $muPluginDir = PucFactory::normalizePath(WPMU_PLUGIN_DIR); 178 | $pluginPath = PucFactory::normalizePath($this->pluginAbsolutePath); 179 | } 180 | 181 | $cachedResult = (strpos($pluginPath, $muPluginDir) === 0); 182 | } 183 | 184 | return $cachedResult; 185 | } 186 | } 187 | 188 | endif; 189 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Plugin/PluginInfo.php: -------------------------------------------------------------------------------- 1 | sections = (array)$instance->sections; 63 | $instance->icons = (array)$instance->icons; 64 | 65 | return $instance; 66 | } 67 | 68 | /** 69 | * Very, very basic validation. 70 | * 71 | * @param \StdClass $apiResponse 72 | * @return bool|\WP_Error 73 | */ 74 | protected function validateMetadata($apiResponse) { 75 | if ( 76 | !isset($apiResponse->name, $apiResponse->version) 77 | || empty($apiResponse->name) 78 | || empty($apiResponse->version) 79 | ) { 80 | return new \WP_Error( 81 | 'puc-invalid-metadata', 82 | "The plugin metadata file does not contain the required 'name' and/or 'version' keys." 83 | ); 84 | } 85 | return true; 86 | } 87 | 88 | 89 | /** 90 | * Transform plugin info into the format used by the native WordPress.org API 91 | * 92 | * @return object 93 | */ 94 | public function toWpFormat(){ 95 | $info = new \stdClass; 96 | 97 | //The custom update API is built so that many fields have the same name and format 98 | //as those returned by the native WordPress.org API. These can be assigned directly. 99 | $sameFormat = array( 100 | 'name', 'slug', 'version', 'requires', 'tested', 'rating', 'upgrade_notice', 101 | 'num_ratings', 'downloaded', 'active_installs', 'homepage', 'last_updated', 102 | 'requires_php', 103 | ); 104 | foreach($sameFormat as $field){ 105 | if ( isset($this->$field) ) { 106 | $info->$field = $this->$field; 107 | } else { 108 | $info->$field = null; 109 | } 110 | } 111 | 112 | //Other fields need to be renamed and/or transformed. 113 | $info->download_link = $this->download_url; 114 | $info->author = $this->getFormattedAuthor(); 115 | $info->sections = array_merge(array('description' => ''), $this->sections); 116 | 117 | if ( !empty($this->banners) ) { 118 | //WP expects an array with two keys: "high" and "low". Both are optional. 119 | //Docs: https://wordpress.org/plugins/about/faq/#banners 120 | $info->banners = is_object($this->banners) ? get_object_vars($this->banners) : $this->banners; 121 | $info->banners = array_intersect_key($info->banners, array('high' => true, 'low' => true)); 122 | } 123 | 124 | return $info; 125 | } 126 | 127 | protected function getFormattedAuthor() { 128 | if ( !empty($this->author_homepage) ){ 129 | /** @noinspection HtmlUnknownTarget */ 130 | return sprintf('%s', $this->author_homepage, $this->author); 131 | } 132 | return $this->author; 133 | } 134 | } 135 | 136 | endif; 137 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Plugin/Update.php: -------------------------------------------------------------------------------- 1 | copyFields($object, $update); 66 | return $update; 67 | } 68 | 69 | /** 70 | * @return string[] 71 | */ 72 | protected function getFieldNames() { 73 | return array_merge(parent::getFieldNames(), self::$extraFields); 74 | } 75 | 76 | /** 77 | * Transform the update into the format used by WordPress native plugin API. 78 | * 79 | * @return object 80 | */ 81 | public function toWpFormat() { 82 | $update = parent::toWpFormat(); 83 | 84 | $update->id = $this->id; 85 | $update->url = $this->homepage; 86 | $update->tested = $this->tested; 87 | $update->requires_php = $this->requires_php; 88 | $update->plugin = $this->filename; 89 | 90 | if ( !empty($this->upgrade_notice) ) { 91 | $update->upgrade_notice = $this->upgrade_notice; 92 | } 93 | 94 | if ( !empty($this->icons) && is_array($this->icons) ) { 95 | //This should be an array with up to 4 keys: 'svg', '1x', '2x' and 'default'. 96 | //Docs: https://developer.wordpress.org/plugins/wordpress-org/plugin-assets/#plugin-icons 97 | $icons = array_intersect_key( 98 | $this->icons, 99 | array('svg' => true, '1x' => true, '2x' => true, 'default' => true) 100 | ); 101 | if ( !empty($icons) ) { 102 | $update->icons = $icons; 103 | 104 | //It appears that the 'default' icon isn't used anywhere in WordPress 4.9, 105 | //but lets set it just in case a future release needs it. 106 | if ( !isset($update->icons['default']) ) { 107 | $update->icons['default'] = current($update->icons); 108 | } 109 | } 110 | } 111 | 112 | return $update; 113 | } 114 | } 115 | 116 | endif; 117 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/StateStore.php: -------------------------------------------------------------------------------- 1 | optionName = $optionName; 34 | } 35 | 36 | /** 37 | * Get time elapsed since the last update check. 38 | * 39 | * If there are no recorded update checks, this method returns a large arbitrary number 40 | * (i.e. time since the Unix epoch). 41 | * 42 | * @return int Elapsed time in seconds. 43 | */ 44 | public function timeSinceLastCheck() { 45 | $this->lazyLoad(); 46 | return time() - $this->lastCheck; 47 | } 48 | 49 | /** 50 | * @return int 51 | */ 52 | public function getLastCheck() { 53 | $this->lazyLoad(); 54 | return $this->lastCheck; 55 | } 56 | 57 | /** 58 | * Set the time of the last update check to the current timestamp. 59 | * 60 | * @return $this 61 | */ 62 | public function setLastCheckToNow() { 63 | $this->lazyLoad(); 64 | $this->lastCheck = time(); 65 | return $this; 66 | } 67 | 68 | /** 69 | * @return null|Update 70 | */ 71 | public function getUpdate() { 72 | $this->lazyLoad(); 73 | return $this->update; 74 | } 75 | 76 | /** 77 | * @param Update|null $update 78 | * @return $this 79 | */ 80 | public function setUpdate(Update $update = null) { 81 | $this->lazyLoad(); 82 | $this->update = $update; 83 | return $this; 84 | } 85 | 86 | /** 87 | * @return string 88 | */ 89 | public function getCheckedVersion() { 90 | $this->lazyLoad(); 91 | return $this->checkedVersion; 92 | } 93 | 94 | /** 95 | * @param string $version 96 | * @return $this 97 | */ 98 | public function setCheckedVersion($version) { 99 | $this->lazyLoad(); 100 | $this->checkedVersion = strval($version); 101 | return $this; 102 | } 103 | 104 | /** 105 | * Get translation updates. 106 | * 107 | * @return array 108 | */ 109 | public function getTranslations() { 110 | $this->lazyLoad(); 111 | if ( isset($this->update, $this->update->translations) ) { 112 | return $this->update->translations; 113 | } 114 | return array(); 115 | } 116 | 117 | /** 118 | * Set translation updates. 119 | * 120 | * @param array $translationUpdates 121 | */ 122 | public function setTranslations($translationUpdates) { 123 | $this->lazyLoad(); 124 | if ( isset($this->update) ) { 125 | $this->update->translations = $translationUpdates; 126 | $this->save(); 127 | } 128 | } 129 | 130 | public function save() { 131 | $state = new \stdClass(); 132 | 133 | $state->lastCheck = $this->lastCheck; 134 | $state->checkedVersion = $this->checkedVersion; 135 | 136 | if ( isset($this->update)) { 137 | $state->update = $this->update->toStdClass(); 138 | 139 | $updateClass = get_class($this->update); 140 | $state->updateClass = $updateClass; 141 | $prefix = $this->getLibPrefix(); 142 | if ( Utils::startsWith($updateClass, $prefix) ) { 143 | $state->updateBaseClass = substr($updateClass, strlen($prefix)); 144 | } 145 | } 146 | 147 | update_site_option($this->optionName, $state); 148 | $this->isLoaded = true; 149 | } 150 | 151 | /** 152 | * @return $this 153 | */ 154 | public function lazyLoad() { 155 | if ( !$this->isLoaded ) { 156 | $this->load(); 157 | } 158 | return $this; 159 | } 160 | 161 | protected function load() { 162 | $this->isLoaded = true; 163 | 164 | $state = get_site_option($this->optionName, null); 165 | 166 | if ( 167 | !is_object($state) 168 | //Sanity check: If the Utils class is missing, the plugin is probably in the process 169 | //of being deleted (e.g. the old version gets deleted during an update). 170 | || !class_exists(Utils::class) 171 | ) { 172 | $this->lastCheck = 0; 173 | $this->checkedVersion = ''; 174 | $this->update = null; 175 | return; 176 | } 177 | 178 | $this->lastCheck = intval(Utils::get($state, 'lastCheck', 0)); 179 | $this->checkedVersion = Utils::get($state, 'checkedVersion', ''); 180 | $this->update = null; 181 | 182 | if ( isset($state->update) ) { 183 | //This mess is due to the fact that the want the update class from this version 184 | //of the library, not the version that saved the update. 185 | 186 | $updateClass = null; 187 | if ( isset($state->updateBaseClass) ) { 188 | $updateClass = $this->getLibPrefix() . $state->updateBaseClass; 189 | } else if ( isset($state->updateClass) ) { 190 | $updateClass = $state->updateClass; 191 | } 192 | 193 | $factory = array($updateClass, 'fromObject'); 194 | if ( ($updateClass !== null) && is_callable($factory) ) { 195 | $this->update = call_user_func($factory, $state->update); 196 | } 197 | } 198 | } 199 | 200 | public function delete() { 201 | delete_site_option($this->optionName); 202 | 203 | $this->lastCheck = 0; 204 | $this->checkedVersion = ''; 205 | $this->update = null; 206 | } 207 | 208 | private function getLibPrefix() { 209 | //This assumes that the current class is at the top of the versioned namespace. 210 | return __NAMESPACE__ . '\\'; 211 | } 212 | } 213 | 214 | endif; 215 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Theme/Package.php: -------------------------------------------------------------------------------- 1 | stylesheet = $stylesheet; 21 | $this->theme = wp_get_theme($this->stylesheet); 22 | 23 | parent::__construct($updateChecker); 24 | } 25 | 26 | public function getInstalledVersion() { 27 | return $this->theme->get('Version'); 28 | } 29 | 30 | public function getAbsoluteDirectoryPath() { 31 | if ( method_exists($this->theme, 'get_stylesheet_directory') ) { 32 | return $this->theme->get_stylesheet_directory(); //Available since WP 3.4. 33 | } 34 | return get_theme_root($this->stylesheet) . '/' . $this->stylesheet; 35 | } 36 | 37 | /** 38 | * Get the value of a specific plugin or theme header. 39 | * 40 | * @param string $headerName 41 | * @param string $defaultValue 42 | * @return string Either the value of the header, or $defaultValue if the header doesn't exist or is empty. 43 | */ 44 | public function getHeaderValue($headerName, $defaultValue = '') { 45 | $value = $this->theme->get($headerName); 46 | if ( ($headerName === false) || ($headerName === '') ) { 47 | return $defaultValue; 48 | } 49 | return $value; 50 | } 51 | 52 | protected function getHeaderNames() { 53 | return array( 54 | 'Name' => 'Theme Name', 55 | 'ThemeURI' => 'Theme URI', 56 | 'Description' => 'Description', 57 | 'Author' => 'Author', 58 | 'AuthorURI' => 'Author URI', 59 | 'Version' => 'Version', 60 | 'Template' => 'Template', 61 | 'Status' => 'Status', 62 | 'Tags' => 'Tags', 63 | 'TextDomain' => 'Text Domain', 64 | 'DomainPath' => 'Domain Path', 65 | ); 66 | } 67 | } 68 | 69 | endif; 70 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Theme/Update.php: -------------------------------------------------------------------------------- 1 | $this->slug, 23 | 'new_version' => $this->version, 24 | 'url' => $this->details_url, 25 | ); 26 | 27 | if ( !empty($this->download_url) ) { 28 | $update['package'] = $this->download_url; 29 | } 30 | 31 | return $update; 32 | } 33 | 34 | /** 35 | * Create a new instance of Theme_Update from its JSON-encoded representation. 36 | * 37 | * @param string $json Valid JSON string representing a theme information object. 38 | * @return self New instance of ThemeUpdate, or NULL on error. 39 | */ 40 | public static function fromJson($json) { 41 | $instance = new self(); 42 | if ( !parent::createFromJson($json, $instance) ) { 43 | return null; 44 | } 45 | return $instance; 46 | } 47 | 48 | /** 49 | * Create a new instance by copying the necessary fields from another object. 50 | * 51 | * @param \StdClass|self $object The source object. 52 | * @return self The new copy. 53 | */ 54 | public static function fromObject($object) { 55 | $update = new self(); 56 | $update->copyFields($object, $update); 57 | return $update; 58 | } 59 | 60 | /** 61 | * Basic validation. 62 | * 63 | * @param \StdClass $apiResponse 64 | * @return bool|\WP_Error 65 | */ 66 | protected function validateMetadata($apiResponse) { 67 | $required = array('version', 'details_url'); 68 | foreach($required as $key) { 69 | if ( !isset($apiResponse->$key) || empty($apiResponse->$key) ) { 70 | return new \WP_Error( 71 | 'tuc-invalid-metadata', 72 | sprintf('The theme metadata is missing the required "%s" key.', $key) 73 | ); 74 | } 75 | } 76 | return true; 77 | } 78 | 79 | protected function getFieldNames() { 80 | return array_merge(parent::getFieldNames(), self::$extraFields); 81 | } 82 | 83 | protected function getPrefixedFilter($tag) { 84 | return parent::getPrefixedFilter($tag) . '_theme'; 85 | } 86 | } 87 | 88 | endif; 89 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Theme/UpdateChecker.php: -------------------------------------------------------------------------------- 1 | stylesheet = $stylesheet; 27 | 28 | parent::__construct( 29 | $metadataUrl, 30 | $stylesheet, 31 | $customSlug ? $customSlug : $stylesheet, 32 | $checkPeriod, 33 | $optionName 34 | ); 35 | } 36 | 37 | /** 38 | * For themes, the update array is indexed by theme directory name. 39 | * 40 | * @return string 41 | */ 42 | protected function getUpdateListKey() { 43 | return $this->directoryName; 44 | } 45 | 46 | /** 47 | * Retrieve the latest update (if any) from the configured API endpoint. 48 | * 49 | * @return Update|null An instance of Update, or NULL when no updates are available. 50 | */ 51 | public function requestUpdate() { 52 | list($themeUpdate, $result) = $this->requestMetadata(Update::class, 'request_update'); 53 | 54 | if ( $themeUpdate !== null ) { 55 | /** @var Update $themeUpdate */ 56 | $themeUpdate->slug = $this->slug; 57 | } 58 | 59 | $themeUpdate = $this->filterUpdateResult($themeUpdate, $result); 60 | return $themeUpdate; 61 | } 62 | 63 | protected function getNoUpdateItemFields() { 64 | return array_merge( 65 | parent::getNoUpdateItemFields(), 66 | array( 67 | 'theme' => $this->directoryName, 68 | 'requires' => '', 69 | ) 70 | ); 71 | } 72 | 73 | public function userCanInstallUpdates() { 74 | return current_user_can('update_themes'); 75 | } 76 | 77 | /** 78 | * Create an instance of the scheduler. 79 | * 80 | * @param int $checkPeriod 81 | * @return Scheduler 82 | */ 83 | protected function createScheduler($checkPeriod) { 84 | return new Scheduler($this, $checkPeriod, array('load-themes.php')); 85 | } 86 | 87 | /** 88 | * Is there an update being installed right now for this theme? 89 | * 90 | * @param \WP_Upgrader|null $upgrader The upgrader that's performing the current update. 91 | * @return bool 92 | */ 93 | public function isBeingUpgraded($upgrader = null) { 94 | return $this->upgraderStatus->isThemeBeingUpgraded($this->stylesheet, $upgrader); 95 | } 96 | 97 | protected function createDebugBarExtension() { 98 | return new DebugBar\Extension($this, DebugBar\ThemePanel::class); 99 | } 100 | 101 | /** 102 | * Register a callback for filtering query arguments. 103 | * 104 | * The callback function should take one argument - an associative array of query arguments. 105 | * It should return a modified array of query arguments. 106 | * 107 | * @param callable $callback 108 | * @return void 109 | */ 110 | public function addQueryArgFilter($callback){ 111 | $this->addFilter('request_update_query_args', $callback); 112 | } 113 | 114 | /** 115 | * Register a callback for filtering arguments passed to wp_remote_get(). 116 | * 117 | * The callback function should take one argument - an associative array of arguments - 118 | * and return a modified array or arguments. See the WP documentation on wp_remote_get() 119 | * for details on what arguments are available and how they work. 120 | * 121 | * @uses add_filter() This method is a convenience wrapper for add_filter(). 122 | * 123 | * @param callable $callback 124 | * @return void 125 | */ 126 | public function addHttpRequestArgFilter($callback) { 127 | $this->addFilter('request_update_options', $callback); 128 | } 129 | 130 | /** 131 | * Register a callback for filtering theme updates retrieved from the external API. 132 | * 133 | * The callback function should take two arguments. If the theme update was retrieved 134 | * successfully, the first argument passed will be an instance of Theme_Update. Otherwise, 135 | * it will be NULL. The second argument will be the corresponding return value of 136 | * wp_remote_get (see WP docs for details). 137 | * 138 | * The callback function should return a new or modified instance of Theme_Update or NULL. 139 | * 140 | * @uses add_filter() This method is a convenience wrapper for add_filter(). 141 | * 142 | * @param callable $callback 143 | * @return void 144 | */ 145 | public function addResultFilter($callback) { 146 | $this->addFilter('request_update_result', $callback, 10, 2); 147 | } 148 | 149 | /** 150 | * Create a package instance that represents this plugin or theme. 151 | * 152 | * @return InstalledPackage 153 | */ 154 | protected function createInstalledPackage() { 155 | return new Package($this->stylesheet, $this); 156 | } 157 | } 158 | 159 | endif; 160 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Update.php: -------------------------------------------------------------------------------- 1 | slug = $this->slug; 31 | $update->new_version = $this->version; 32 | $update->package = $this->download_url; 33 | 34 | return $update; 35 | } 36 | } 37 | 38 | endif; 39 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Utils.php: -------------------------------------------------------------------------------- 1 | $node) ) { 27 | $currentValue = $currentValue->$node; 28 | } else { 29 | return $default; 30 | } 31 | } 32 | 33 | return $currentValue; 34 | } 35 | 36 | /** 37 | * Get the first array element that is not empty. 38 | * 39 | * @param array $values 40 | * @param mixed|null $default Returns this value if there are no non-empty elements. 41 | * @return mixed|null 42 | */ 43 | public static function findNotEmpty($values, $default = null) { 44 | if ( empty($values) ) { 45 | return $default; 46 | } 47 | 48 | foreach ($values as $value) { 49 | if ( !empty($value) ) { 50 | return $value; 51 | } 52 | } 53 | 54 | return $default; 55 | } 56 | 57 | /** 58 | * Check if the input string starts with the specified prefix. 59 | * 60 | * @param string $input 61 | * @param string $prefix 62 | * @return bool 63 | */ 64 | public static function startsWith($input, $prefix) { 65 | $length = strlen($prefix); 66 | return (substr($input, 0, $length) === $prefix); 67 | } 68 | } 69 | 70 | endif; 71 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Vcs/BaseChecker.php: -------------------------------------------------------------------------------- 1 | properties = $properties; 23 | } 24 | 25 | /** 26 | * @param string $name 27 | * @return mixed|null 28 | */ 29 | public function __get($name) { 30 | return array_key_exists($name, $this->properties) ? $this->properties[$name] : null; 31 | } 32 | 33 | /** 34 | * @param string $name 35 | * @param mixed $value 36 | */ 37 | public function __set($name, $value) { 38 | $this->properties[$name] = $value; 39 | } 40 | 41 | /** 42 | * @param string $name 43 | * @return bool 44 | */ 45 | public function __isset($name) { 46 | return isset($this->properties[$name]); 47 | } 48 | 49 | } 50 | 51 | endif; 52 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Vcs/ReleaseAssetSupport.php: -------------------------------------------------------------------------------- 1 | releaseAssetsEnabled = true; 40 | $this->assetFilterRegex = $nameRegex; 41 | $this->releaseAssetPreference = $preference; 42 | } 43 | 44 | /** 45 | * Disable release assets. 46 | * 47 | * @return void 48 | * @noinspection PhpUnused -- Public API 49 | */ 50 | public function disableReleaseAssets() { 51 | $this->releaseAssetsEnabled = false; 52 | $this->assetFilterRegex = null; 53 | } 54 | 55 | /** 56 | * Does the specified asset match the name regex? 57 | * 58 | * @param mixed $releaseAsset Data type and structure depend on the host/API. 59 | * @return bool 60 | */ 61 | protected function matchesAssetFilter($releaseAsset) { 62 | if ( $this->assetFilterRegex === null ) { 63 | //The default is to accept all assets. 64 | return true; 65 | } 66 | 67 | $name = $this->getFilterableAssetName($releaseAsset); 68 | if ( !is_string($name) ) { 69 | return false; 70 | } 71 | return (bool)preg_match($this->assetFilterRegex, $releaseAsset->name); 72 | } 73 | 74 | /** 75 | * Get the part of asset data that will be checked against the filter regex. 76 | * 77 | * @param mixed $releaseAsset 78 | * @return string|null 79 | */ 80 | abstract protected function getFilterableAssetName($releaseAsset); 81 | } 82 | 83 | endif; -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Vcs/ReleaseFilteringFeature.php: -------------------------------------------------------------------------------- 1 | 100 ) { 39 | throw new \InvalidArgumentException(sprintf( 40 | 'The max number of releases is too high (%d). It must be 100 or less.', 41 | $maxReleases 42 | )); 43 | } else if ( $maxReleases < 1 ) { 44 | throw new \InvalidArgumentException(sprintf( 45 | 'The max number of releases is too low (%d). It must be at least 1.', 46 | $maxReleases 47 | )); 48 | } 49 | 50 | $this->releaseFilterCallback = $callback; 51 | $this->releaseFilterByType = $releaseTypes; 52 | $this->releaseFilterMaxReleases = $maxReleases; 53 | return $this; 54 | } 55 | 56 | /** 57 | * Filter releases by their version number. 58 | * 59 | * @param string $regex A regular expression. The release version number must match this regex. 60 | * @param int $releaseTypes 61 | * @param int $maxReleasesToExamine 62 | * @return $this 63 | * @noinspection PhpUnused -- Public API 64 | */ 65 | public function setReleaseVersionFilter( 66 | $regex, 67 | $releaseTypes = Api::RELEASE_FILTER_SKIP_PRERELEASE, 68 | $maxReleasesToExamine = 20 69 | ) { 70 | return $this->setReleaseFilter( 71 | function ($versionNumber) use ($regex) { 72 | return (preg_match($regex, $versionNumber) === 1); 73 | }, 74 | $releaseTypes, 75 | $maxReleasesToExamine 76 | ); 77 | } 78 | 79 | /** 80 | * @param string $versionNumber The detected release version number. 81 | * @param object $releaseObject Varies depending on the host/API. 82 | * @return bool 83 | */ 84 | protected function matchesCustomReleaseFilter($versionNumber, $releaseObject) { 85 | if ( !is_callable($this->releaseFilterCallback) ) { 86 | return true; //No custom filter. 87 | } 88 | return call_user_func($this->releaseFilterCallback, $versionNumber, $releaseObject); 89 | } 90 | 91 | /** 92 | * @return bool 93 | */ 94 | protected function shouldSkipPreReleases() { 95 | //Maybe this could be a bitfield in the future, if we need to support 96 | //more release types. 97 | return ($this->releaseFilterByType !== Api::RELEASE_FILTER_ALL); 98 | } 99 | 100 | /** 101 | * @return bool 102 | */ 103 | protected function hasCustomReleaseFilter() { 104 | return isset($this->releaseFilterCallback) && is_callable($this->releaseFilterCallback); 105 | } 106 | } 107 | 108 | endif; -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Vcs/ThemeUpdateChecker.php: -------------------------------------------------------------------------------- 1 | api = $api; 24 | 25 | parent::__construct($api->getRepositoryUrl(), $stylesheet, $customSlug, $checkPeriod, $optionName); 26 | 27 | $this->api->setHttpFilterName($this->getUniqueName('request_update_options')); 28 | $this->api->setStrategyFilterName($this->getUniqueName('vcs_update_detection_strategies')); 29 | $this->api->setSlug($this->slug); 30 | } 31 | 32 | public function requestUpdate() { 33 | $api = $this->api; 34 | $api->setLocalDirectory($this->package->getAbsoluteDirectoryPath()); 35 | 36 | $update = new Theme\Update(); 37 | $update->slug = $this->slug; 38 | 39 | //Figure out which reference (tag or branch) we'll use to get the latest version of the theme. 40 | $updateSource = $api->chooseReference($this->branch); 41 | if ( $updateSource ) { 42 | $ref = $updateSource->name; 43 | $update->download_url = $updateSource->downloadUrl; 44 | } else { 45 | do_action( 46 | 'puc_api_error', 47 | new \WP_Error( 48 | 'puc-no-update-source', 49 | 'Could not retrieve version information from the repository. ' 50 | . 'This usually means that the update checker either can\'t connect ' 51 | . 'to the repository or it\'s configured incorrectly.' 52 | ), 53 | null, null, $this->slug 54 | ); 55 | $ref = $this->branch; 56 | } 57 | 58 | //Get headers from the main stylesheet in this branch/tag. Its "Version" header and other metadata 59 | //are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags. 60 | $remoteHeader = $this->package->getFileHeader($api->getRemoteFile('style.css', $ref)); 61 | $update->version = Utils::findNotEmpty(array( 62 | $remoteHeader['Version'], 63 | Utils::get($updateSource, 'version'), 64 | )); 65 | 66 | //The details URL defaults to the Theme URI header or the repository URL. 67 | $update->details_url = Utils::findNotEmpty(array( 68 | $remoteHeader['ThemeURI'], 69 | $this->package->getHeaderValue('ThemeURI'), 70 | $this->metadataUrl, 71 | )); 72 | 73 | if ( empty($update->version) ) { 74 | //It looks like we didn't find a valid update after all. 75 | $update = null; 76 | } 77 | 78 | $update = $this->filterUpdateResult($update); 79 | return $update; 80 | } 81 | } 82 | 83 | endif; 84 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/Vcs/VcsCheckerMethods.php: -------------------------------------------------------------------------------- 1 | branch = $branch; 20 | return $this; 21 | } 22 | 23 | /** 24 | * Set authentication credentials. 25 | * 26 | * @param array|string $credentials 27 | * @return $this 28 | */ 29 | public function setAuthentication($credentials) { 30 | $this->api->setAuthentication($credentials); 31 | return $this; 32 | } 33 | 34 | /** 35 | * @return Api 36 | */ 37 | public function getVcsApi() { 38 | return $this->api; 39 | } 40 | 41 | public function getUpdate() { 42 | $update = parent::getUpdate(); 43 | 44 | if ( isset($update) && !empty($update->download_url) ) { 45 | $update->download_url = $this->api->signDownloadUrl($update->download_url); 46 | } 47 | 48 | return $update; 49 | } 50 | 51 | public function onDisplayConfiguration($panel) { 52 | parent::onDisplayConfiguration($panel); 53 | $panel->row('Branch', $this->branch); 54 | $panel->row('Authentication enabled', $this->api->isAuthenticationEnabled() ? 'Yes' : 'No'); 55 | $panel->row('API client', get_class($this->api)); 56 | } 57 | } 58 | 59 | endif; -------------------------------------------------------------------------------- /vendor/plugin-update-checker/Puc/v5p4/WpCliCheckTrigger.php: -------------------------------------------------------------------------------- 1 | componentType = $componentType; 42 | $this->scheduler = $scheduler; 43 | 44 | if ( !defined('WP_CLI') || !class_exists(WP_CLI::class, false) ) { 45 | return; //Nothing to do if WP-CLI is not available. 46 | } 47 | 48 | /* 49 | * We can't hook directly into wp_update_plugins(), but we can hook into the WP-CLI 50 | * commands that call it. We'll use the "before_invoke:xyz" hook to trigger update checks. 51 | */ 52 | foreach ($this->getRelevantCommands() as $command) { 53 | WP_CLI::add_hook('before_invoke:' . $command, [$this, 'triggerUpdateCheckOnce']); 54 | } 55 | } 56 | 57 | private function getRelevantCommands() { 58 | $result = []; 59 | foreach (['status', 'list', 'update'] as $subcommand) { 60 | $result[] = $this->componentType . ' ' . $subcommand; 61 | } 62 | return $result; 63 | } 64 | 65 | /** 66 | * Trigger a potential update check once. 67 | * 68 | * @param mixed $input 69 | * @return mixed The input value, unchanged. 70 | * @internal This method is public so that it can be used as a WP-CLI hook callback. 71 | * It should not be called directly. 72 | * 73 | */ 74 | public function triggerUpdateCheckOnce($input = null) { 75 | if ( $this->wasCheckTriggered ) { 76 | return $input; 77 | } 78 | 79 | $this->wasCheckTriggered = true; 80 | $this->scheduler->maybeCheckForUpdates(); 81 | 82 | return $input; 83 | } 84 | } -------------------------------------------------------------------------------- /vendor/plugin-update-checker/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yahnis-elsts/plugin-update-checker", 3 | "type": "library", 4 | "description": "A custom update checker for WordPress plugins and themes. Useful if you can't host your plugin in the official WP repository but still want it to support automatic updates.", 5 | "keywords": ["wordpress", "plugin updates", "automatic updates", "theme updates"], 6 | "homepage": "https://github.com/YahnisElsts/plugin-update-checker/", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Yahnis Elsts", 11 | "email": "whiteshadow@w-shadow.com", 12 | "homepage": "https://w-shadow.com/", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.6.20", 18 | "ext-json": "*" 19 | }, 20 | "autoload": { 21 | "files": ["load-v5p4.php"] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/css/puc-debug-bar.css: -------------------------------------------------------------------------------- 1 | .puc-debug-bar-panel-v5 pre { 2 | margin-top: 0; 3 | } 4 | 5 | /* Style the debug data table to match "widefat" table style used by WordPress. */ 6 | table.puc-debug-data { 7 | width: 100%; 8 | clear: both; 9 | margin: 0; 10 | 11 | border-spacing: 0; 12 | background-color: #f9f9f9; 13 | 14 | border-radius: 3px; 15 | border: 1px solid #dfdfdf; 16 | border-collapse: separate; 17 | } 18 | 19 | table.puc-debug-data * { 20 | word-wrap: break-word; 21 | } 22 | 23 | table.puc-debug-data th { 24 | width: 11em; 25 | padding: 7px 7px 8px; 26 | text-align: left; 27 | 28 | font-family: "Georgia", "Times New Roman", "Bitstream Charter", "Times", serif; 29 | font-weight: 400; 30 | font-size: 14px; 31 | line-height: 1.3em; 32 | text-shadow: rgba(255, 255, 255, 0.804) 0 1px 0; 33 | } 34 | 35 | table.puc-debug-data td, table.puc-debug-data th { 36 | border-width: 1px 0; 37 | border-style: solid; 38 | 39 | border-top-color: #fff; 40 | border-bottom-color: #dfdfdf; 41 | 42 | text-transform: none; 43 | } 44 | 45 | table.puc-debug-data td { 46 | color: #555; 47 | font-size: 12px; 48 | padding: 4px 7px 2px; 49 | vertical-align: top; 50 | } 51 | 52 | .puc-ajax-response { 53 | border: 1px solid #dfdfdf; 54 | border-radius: 3px; 55 | padding: 0.5em; 56 | margin: 5px 0; 57 | background-color: white; 58 | } 59 | 60 | .puc-ajax-nonce { 61 | display: none; 62 | } 63 | 64 | .puc-ajax-response dt { 65 | margin: 0; 66 | } 67 | 68 | .puc-ajax-response dd { 69 | margin: 0 0 1em; 70 | } 71 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/js/debug-bar.js: -------------------------------------------------------------------------------- 1 | jQuery(function($) { 2 | 3 | function runAjaxAction(button, action) { 4 | button = $(button); 5 | var panel = button.closest('.puc-debug-bar-panel-v5'); 6 | var responseBox = button.closest('td').find('.puc-ajax-response'); 7 | 8 | responseBox.text('Processing...').show(); 9 | $.post( 10 | ajaxurl, 11 | { 12 | action : action, 13 | uid : panel.data('uid'), 14 | _wpnonce: panel.data('nonce') 15 | }, 16 | function(data) { 17 | //The response contains HTML that should already be escaped in server-side code. 18 | //phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html 19 | responseBox.html(data); 20 | }, 21 | 'html' 22 | ); 23 | } 24 | 25 | $('.puc-debug-bar-panel-v5 input[name="puc-check-now-button"]').on('click', function() { 26 | runAjaxAction(this, 'puc_v5_debug_check_now'); 27 | return false; 28 | }); 29 | 30 | $('.puc-debug-bar-panel-v5 input[name="puc-request-info-button"]').on('click', function() { 31 | runAjaxAction(this, 'puc_v5_debug_request_info'); 32 | return false; 33 | }); 34 | 35 | 36 | // Debug Bar uses the panel class name as part of its link and container IDs. This means we can 37 | // end up with multiple identical IDs if more than one plugin uses the update checker library. 38 | // Fix it by replacing the class name with the plugin slug. 39 | var panels = $('#debug-menu-targets').find('.puc-debug-bar-panel-v5'); 40 | panels.each(function() { 41 | var panel = $(this); 42 | var uid = panel.data('uid'); 43 | var target = panel.closest('.debug-menu-target'); 44 | 45 | //Change the panel wrapper ID. 46 | target.attr('id', 'debug-menu-target-puc-' + uid); 47 | 48 | //Change the menu link ID as well and point it at the new target ID. 49 | $('#debug-bar-menu').find('.puc-debug-menu-link-' + uid) 50 | .closest('.debug-menu-link') 51 | .attr('id', 'debug-menu-link-puc-' + uid) 52 | .attr('href', '#' + target.attr('id')); 53 | }); 54 | }); -------------------------------------------------------------------------------- /vendor/plugin-update-checker/languages/plugin-update-checker-ca.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertdevore/back-in-stock-notifications/27c0946952055cb66ebd7ec53e0aa8374b969057/vendor/plugin-update-checker/languages/plugin-update-checker-ca.mo -------------------------------------------------------------------------------- /vendor/plugin-update-checker/languages/plugin-update-checker-ca.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: plugin-update-checker\n" 4 | "POT-Creation-Date: 2017-11-24 17:02+0200\n" 5 | "PO-Revision-Date: 2019-09-25 18:15+0200\n" 6 | "Language-Team: \n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "X-Generator: Poedit 2.2.3\n" 11 | "X-Poedit-Basepath: ..\n" 12 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 13 | "X-Poedit-SourceCharset: UTF-8\n" 14 | "X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n" 15 | "Last-Translator: \n" 16 | "Language: ca\n" 17 | "X-Poedit-SearchPath-0: .\n" 18 | 19 | #: Puc/v4p3/Plugin/UpdateChecker.php:395 20 | msgid "Check for updates" 21 | msgstr "Comprova si hi ha actualitzacions" 22 | 23 | #: Puc/v4p3/Plugin/UpdateChecker.php:548 24 | #, php-format 25 | msgctxt "the plugin title" 26 | msgid "The %s plugin is up to date." 27 | msgstr "L’extensió %s està actualitzada." 28 | 29 | #: Puc/v4p3/Plugin/UpdateChecker.php:550 30 | #, php-format 31 | msgctxt "the plugin title" 32 | msgid "A new version of the %s plugin is available." 33 | msgstr "Una nova versió de l’extensió %s està disponible." 34 | 35 | #: Puc/v4p3/Plugin/UpdateChecker.php:552 36 | #, php-format 37 | msgctxt "the plugin title" 38 | msgid "Could not determine if updates are available for %s." 39 | msgstr "No s’ha pogut determinar si hi ha actualitzacions per a %s." 40 | 41 | #: Puc/v4p3/Plugin/UpdateChecker.php:558 42 | #, php-format 43 | msgid "Unknown update checker status \"%s\"" 44 | msgstr "Estat del comprovador d’actualitzacions desconegut \"%s\"" 45 | 46 | #: Puc/v4p3/Vcs/PluginUpdateChecker.php:95 47 | msgid "There is no changelog available." 48 | msgstr "No hi ha cap registre de canvis disponible." 49 | -------------------------------------------------------------------------------- /vendor/plugin-update-checker/languages/plugin-update-checker-cs_CZ.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertdevore/back-in-stock-notifications/27c0946952055cb66ebd7ec53e0aa8374b969057/vendor/plugin-update-checker/languages/plugin-update-checker-cs_CZ.mo -------------------------------------------------------------------------------- /vendor/plugin-update-checker/languages/plugin-update-checker-cs_CZ.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: plugin-update-checker\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2017-05-20 10:53+0300\n" 6 | "PO-Revision-Date: 2017-07-05 15:39+0000\n" 7 | "Last-Translator: Vojtěch Sajdl
' . wp_kses_post( 66 | sprintf( 67 | __( 68 | 'The plugin has been deactivated because it cannot be used on WordPress.com-hosted websites. %s', 69 | 'wpcom-plugin-check' 70 | ), 71 | '' . __( 'Learn more', 'wpcom-plugin-check' ) . '' 72 | ) 73 | ) . '
'; 74 | echo '%s
', 90 | __( 'Plugin Activation Blocked', 'wpcom-plugin-check' ), 91 | __( 'This plugin cannot be activated on WordPress.com-hosted websites. It is restricted due to concerns about WordPress.com policies impacting the community.', 'wpcom-plugin-check' ), 92 | esc_url( $this->learnMoreLink ), 93 | __( 'Learn more', 'wpcom-plugin-check' ) 94 | ) 95 | ), 96 | esc_html__('Plugin Activation Blocked', 'wpcom-plugin-check'), 97 | [ 'back_link' => true ] 98 | ); 99 | } 100 | } 101 | 102 | /** 103 | * Set a flag when the plugin is deactivated. 104 | * 105 | * @return void 106 | */ 107 | public function setDeactivationFlag(): void { 108 | add_option( 'wpcom_plugin_deactivation_notice', $this->learnMoreLink ); 109 | } 110 | } 111 | 112 | // Initialize the class. 113 | new WPComPluginHandler( plugin_basename( __FILE__ ), 'https://example.com/community-statement' ); 114 | --------------------------------------------------------------------------------