Plugin Directory

Changeset 3356445


Ignore:
Timestamp:
09/05/2025 05:31:39 AM (6 months ago)
Author:
picocodes
Message:

Update to version 1.2.2 from GitHub

Location:
hizzle-downloads
Files:
18 edited
1 copied

Legend:

Unmodified
Added
Removed
  • hizzle-downloads/tags/1.2.2/hizzle-downloads.php

    r3287466 r3356445  
    44 * Plugin URI: https://hizzle.co/download-manager/
    55 * Description: A lightweight download manager plugin.
    6  * Version: 1.2.1
     6 * Version: 1.2.2
    77 * Author: Hizzle
    88 * Author URI: https://hizzle.co
     
    2222
    2323if ( ! defined( 'HIZZLE_DOWNLOADS_VERSION' ) ) {
    24     define( 'HIZZLE_DOWNLOADS_VERSION', '1.2.1' );
     24    define( 'HIZZLE_DOWNLOADS_VERSION', '1.2.2' );
    2525}
    2626
  • hizzle-downloads/tags/1.2.2/readme.txt

    r3287466 r3356445  
    11=== Simple Download Manager - Hizzle Downloads ===
    22Contributors: picocodes, mutendebrian
    3 Tags: files, downloads, digital downloads
     3Tags: files, downloads, digital downloads, download manager, restrict downloads
    44Requires at least: 4.9
    55Tested up to: 6.7
    66Requires PHP: 5.6
    7 Version: 1.2.1
    8 Stable tag: 1.2.1
     7Version: 1.2.2
     8Stable tag: 1.2.2
    99License: GPLv3
    1010License URI: https://www.gnu.org/licenses/gpl-3.0.html
    1111Donate link: https://noptin.com/products/?utm_source=wp-repo&utm_medium=donate&utm_campaign=readme
    1212
    13 Allow your website visitors to download files from your site. Optionally restrict downloads by user role, ip address, etc
     13Easily add, restrict, and track digital downloads in WordPress — protect files with passwords, user roles, IPs, or subscriber access.
    1414
    1515== Description ==
    1616
    17 This plugin allows you to:-
     17**A simple WordPress download manager for secure file sharing, access control, and download tracking — perfect for digital products.**
     18★★★★★<br>
    1819
    19 - Add unlimited downloadable files.
    20 - Optionally protect each downloadable file with a password.
    21 - Restrict downloads to specific user roles.
    22 - Restrict downloads to specific ip addresses.
    23 - Restrict downloads to specific users.
    24 - Restrict downloads to newsletter subscribers.
    25 - Track file downloads.
     20Do you need a simple yet powerful way to manage file downloads on your WordPress site? This plugin makes it easy to upload, organize, and control access to downloadable files of any type. Whether you are sharing free resources, selling digital products, or delivering private documents, this plugin gives you full control over who can download your files and when.
     21
     22With unlimited downloads, flexible restrictions, and detailed tracking, you can confidently provide files to your audience while keeping them secure.
     23
     24= Key Features =
     25
     26- **Add unlimited downloadable files** – Add and manage as many downloadable files as you need, with no limits.
     27- **Password Protection** – Protect individual files with custom passwords so only authorized users can access them.
     28- **Restrict downloads to specific user roles** – Control file access based on WordPress user roles, ensuring that only administrators, editors, subscribers, or custom roles can download.
     29- **Restrict downloads to specific IP addresses** – Restrict downloads to specific IP addresses to prevent abuse or unauthorized sharing.
     30- **Restrict downloads to specific users** – Assign downloads to specific registered users for secure, private file delivery.
     31- **Restrict downloads to newsletter subscribers** – Restrict downloads to Noptin newsletter subscriber, making it an excellent tool for lead generation.
     32- **Track file downloads** – Track every file download with detailed statistics, helping you understand how your files are being accessed.
     33- **Simple Management** – A user-friendly interface makes uploading and managing files straightforward, even for beginners.
     34
     35= Why Use This Plugin? =
     36
     37Managing downloads manually in WordPress can be difficult. Links can be shared publicly, access can’t easily be restricted, and tracking is limited. This plugin solves those problems by giving you advanced tools to:
     38
     39- Protect digital products such as **software, themes, and plugins**.
     40- Share private **PDF documents, contracts, or reports** securely with clients.
     41- Provide exclusive resources like **eBooks, whitepapers, and templates** to email subscribers.
     42- Control access to files for **membership sites and online courses**.
     43- Monitor and analyze download activity to make better business decisions.
     44
     45= Benefits for Your Website =
     46
     47By installing this plugin, you’ll be able to:
     48
     49- Grow your email list by offering subscriber-only downloads.
     50- Monetize your website by controlling access to premium resources.
     51- Increase security by preventing unauthorized downloads and link sharing.
     52- Gain insights into how your downloads are performing.
     53
     54Whether you’re a blogger, developer, marketer, educator, or business owner, this plugin gives you all the tools you need to manage file downloads effectively in WordPress.
     55
     56Take control of your downloads today and provide a seamless, secure experience for your users.
    2657
    2758== Installation ==
     
    3768= Can I see how many times each file has been downloaded? =
    3869
    39 Yes, Noptin allows you to view how many times each file has been downloaded, as well as which users have downloaded the file. This information is available in the Noptin dashboard, where you can see a list of all your downloadable files and their download stats. You can also see which users have downloaded the files, and view their information and activity in your mailing list. This can be useful for tracking the popularity of your files and understanding which users are interested in them.
     70Yes, Hizzle Downloads allows you to view how many times each file has been downloaded, as well as which users have downloaded the file. This information is available in the Hizzle Downloads dashboard, where you can see a list of all your downloadable files and their download stats. You can also see which users have downloaded the files, and view their information and activity in your mailing list. This can be useful for tracking the popularity of your files and understanding which users are interested in them.
    4071
    4172= Can I restrict downloads by user role or newsletter subscription status? =
    4273
    43 Yes, Noptin allows you to restrict downloads by user role or newsletter subscription status. This means that you can choose which users are able to download your files, based on their user role on your website or their status as a newsletter subscriber. For example, you can make a file available only to users who are registered as members on your website, or only to users who have subscribed to your newsletter. This can be useful for creating exclusive content or offers for your users, and for encouraging people to sign up for your newsletter. You can set these restrictions for each of your downloadable files in the Noptin settings.
     74Yes, Hizzle Downloads allows you to restrict downloads by user role or newsletter subscription status. This means that you can choose which users are able to download your files, based on their user role on your website or their status as a newsletter subscriber. For example, you can make a file available only to users who are registered as members on your website, or only to users who have subscribed to your newsletter. This can be useful for creating exclusive content or offers for your users, and for encouraging people to sign up for your newsletter. You can set these restrictions for each of your downloadable files in the Hizzle Downloads settings.
    4475
    4576= How do I display downloadable files? =
     
    4980= How can I get in touch? =
    5081
    51 Use the [contact form on our website](https://hizzle.co/contact-us/).
     82Use the [contact form on our website](https://hizzlewp.com/contact/).
    5283
    5384= How can I contribute? =
     
    6495
    6596== Changelog ==
     97
     98= 1.2.2 =
     99- Update composer packages.
    66100
    67101= 1.2.1 =
     
    100134
    101135= 1.0.3 =
    102 - Restrict downloads to newsletter subscribers.
     136- Restrict downloads to Noptin newsletter subscribers.
    103137- Add support for password protected downloads.
    104138- Automatically update download files when there is a GitHub release.
  • hizzle-downloads/tags/1.2.2/vendor/composer/installed.json

    r3287466 r3356445  
    5454        {
    5555            "name": "hizzle/store",
    56             "version": "0.2.7",
    57             "version_normalized": "0.2.7.0",
     56            "version": "0.2.11",
     57            "version_normalized": "0.2.11.0",
    5858            "source": {
    5959                "type": "git",
    6060                "url": "https://github.com/hizzle-co/datastore.git",
    61                 "reference": "6620679060f7b64e0a417758a328173e8b5ec729"
     61                "reference": "a6e17c589443de791e9bf9eb9c2536c7401f09c2"
    6262            },
    6363            "dist": {
    6464                "type": "zip",
    65                 "url": "https://api.github.com/repos/hizzle-co/datastore/zipball/6620679060f7b64e0a417758a328173e8b5ec729",
    66                 "reference": "6620679060f7b64e0a417758a328173e8b5ec729",
     65                "url": "https://api.github.com/repos/hizzle-co/datastore/zipball/a6e17c589443de791e9bf9eb9c2536c7401f09c2",
     66                "reference": "a6e17c589443de791e9bf9eb9c2536c7401f09c2",
    6767                "shasum": ""
    6868            },
     
    7070                "php": ">=5.3.0"
    7171            },
    72             "time": "2025-05-03T10:20:52+00:00",
     72            "time": "2025-08-20T05:32:06+00:00",
    7373            "type": "library",
    7474            "installation-source": "dist",
     
    9999            "support": {
    100100                "issues": "https://github.com/hizzle-co/datastore/issues",
    101                 "source": "https://github.com/hizzle-co/datastore/tree/0.2.7"
     101                "source": "https://github.com/hizzle-co/datastore/tree/0.2.11"
    102102            },
    103103            "install-path": "../hizzle/store"
  • hizzle-downloads/tags/1.2.2/vendor/composer/installed.php

    r3287466 r3356445  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => 'eacb4c22f68cba9ad5d53a20b1db45c4f10bad5a',
     6        'reference' => '54e05e6de537c0de0a2c4e916ec2394d169ccd55',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => 'eacb4c22f68cba9ad5d53a20b1db45c4f10bad5a',
     16            'reference' => '54e05e6de537c0de0a2c4e916ec2394d169ccd55',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
     
    3030        ),
    3131        'hizzle/store' => array(
    32             'pretty_version' => '0.2.7',
    33             'version' => '0.2.7.0',
    34             'reference' => '6620679060f7b64e0a417758a328173e8b5ec729',
     32            'pretty_version' => '0.2.11',
     33            'version' => '0.2.11.0',
     34            'reference' => 'a6e17c589443de791e9bf9eb9c2536c7401f09c2',
    3535            'type' => 'library',
    3636            'install_path' => __DIR__ . '/../hizzle/store',
  • hizzle-downloads/tags/1.2.2/vendor/hizzle/store/example-plugin.php

    r3287466 r3356445  
    44Plugin URI: https://hizzle.co/
    55Description: Hizzle Data Stores
    6 Version: 0.2.7
     6Version: 0.2.11
    77Author: picocodes
    88Author URI: https://github.com/picocodes/
  • hizzle-downloads/tags/1.2.2/vendor/hizzle/store/src/Collection.php

    r3287466 r3356445  
    427427                $schema .= "PRIMARY KEY  ($cols),\n"; // Maintain 2 spaces between key and opening bracket.
    428428            } elseif ( 'unique' === $index ) {
    429 
    430429                foreach ( $cols as $prop => $index ) {
    431430                    $schema .= "UNIQUE KEY $prop ($index),\n";
     
    727726
    728727    /**
     728     * Returns a prop's cache key.
     729     *
     730     * @param string $prop The prop to get the cache key for.
     731     * @return string The cache key.
     732     */
     733    private function get_prop_cache_key( $prop ) {
     734        $current_version = 'v2'; // Update this version when the cache key structure changes.
     735        return $this->hook_prefix( $current_version . '_ids_by_' . $prop, true );
     736    }
     737
     738    /**
    729739     * Retrieves an ID by a given prop.
    730740     *
     
    741751
    742752        // Try the cache.
    743         $value = trim( $value );
    744         $id    = wp_cache_get( $value, $this->hook_prefix( 'ids_by_' . $prop, true ) );
     753        $value     = trim( $value );
     754        $cache_key = $this->get_prop_cache_key( $prop );
     755        $id        = wp_cache_get( $value, $cache_key );
    745756
    746757        // Maybe retrieve from the db.
     
    753764
    754765            // Update the cache.
    755             wp_cache_set( $value, $id, $this->hook_prefix( 'ids_by_' . $prop, true ) );
     766            wp_cache_set( $value, $id, $cache_key, empty( $id ) ? MINUTE_IN_SECONDS : HOUR_IN_SECONDS );
    756767        }
    757768
     
    778789            // Date fields.
    779790            if ( $value instanceof Date_Time ) {
    780 
    781791                if ( ! empty( $this->props[ $key ] ) && 'date' === strtolower( $this->props[ $key ]->type ) ) {
    782792                    $value = $value->utc( 'Y-m-d' );
     
    846856        $record->apply_changes();
    847857
    848         // Clear the cache.
    849         $this->clear_cache( $record->get_data() );
     858        // Clear any stale cache entries.
     859        $this->clear_cache( $record );
    850860
    851861        // Fires after creating a record.
     
    915925                apply_filters( $this->hook_prefix( 'insert_formats', true ), $formats, $record )
    916926            );
     927
     928            if ( empty( $result ) && ! empty( $wpdb->last_error ) ) {
     929                noptin_error_log( 'Error creating record: ' . $wpdb->last_error );
     930            }
    917931
    918932            return $result ? $wpdb->insert_id : 0;
     
    10131027
    10141028            if ( $prop->is_meta_key_multiple ) {
    1015 
    10161029                $new       = (array) $new;
    10171030                $to_delete = array_diff( $current, $new );
     
    11081121                $this->save_defaults( $record );
    11091122                $raw_data = $record->get_data();
    1110 
    11111123            } elseif ( empty( $raw_data ) ) {
    11121124                $this->not_found();
     
    11241136            // Cache the record data.
    11251137            $this->update_cache( $data );
    1126 
    11271138        }
    11281139
     
    12031214        // Fires before updating a record.
    12041215        do_action( $this->hook_prefix( 'before_update', true ), $record );
     1216
     1217        // Clear cache early to prevent race conditions and stale data
     1218        // Do this before any database operations
     1219        $this->clear_cache( $record );
    12051220
    12061221        $raw_changes = array_keys( $record->get_changes() );
     
    12351250        $record->apply_changes();
    12361251
    1237         // Clear the cache.
    1238         $this->clear_cache( $record->get_data() );
    1239 
    12401252        if ( $has_changes ) {
    12411253
     
    12641276
    12651277        // Invalidate cache.
    1266         $this->clear_cache( $record->get_data() );
     1278        $this->clear_cache( $record );
    12671279
    12681280        // If this is a CPT, delete the post.
     
    15171529
    15181530        // Ensure we have an array.
    1519         if ( ! is_array( $record ) ) {
     1531        if ( ! is_array( $record ) || empty( $record['id'] ) ) {
    15201532            return;
    15211533        }
    15221534
     1535        // Cache lookup values for faster queries
    15231536        foreach ( $this->get_cache_keys() as $key ) {
    15241537            if ( isset( $record[ $key ] ) && ! empty( $record[ $key ] ) ) {
    1525                 wp_cache_set( $record[ $key ], $record['id'], $this->hook_prefix( 'ids_by_' . $key, true ), WEEK_IN_SECONDS );
     1538                wp_cache_set( $record[ $key ], $record['id'], $this->get_prop_cache_key( $key ), HOUR_IN_SECONDS );
    15261539            }
    15271540        }
    15281541
    15291542        // Cache the entire record.
    1530         wp_cache_set( $record['id'], $record, $this->get_full_name(), DAY_IN_SECONDS );
     1543        wp_cache_set( $record['id'], $record, $this->get_full_name(), HOUR_IN_SECONDS );
    15311544    }
    15321545
     
    15341547     * Clean caches.
    15351548     *
    1536      * @param object $record The raw db record.
     1549     * @param Record $record The raw db record.
    15371550     */
    15381551    public function clear_cache( $record ) {
    15391552
    1540         $record = (object) $record;
    1541 
    15421553        foreach ( $this->get_cache_keys() as $key ) {
    1543             if ( ! is_null( $record->$key ) && '' !== $record->$key ) {
    1544                 wp_cache_delete( $record->$key, $this->hook_prefix( 'ids_by_' . $key, true ) );
    1545             }
    1546         }
    1547 
    1548         wp_cache_delete( $record->id, $this->get_full_name() );
     1554            $value = $record->get( $key );
     1555            if ( is_string( $value ) && '' !== $value ) {
     1556                wp_cache_delete( $value, $this->get_prop_cache_key( $key ) );
     1557            }
     1558        }
     1559
     1560        // Bail early if the record doesn't exist.
     1561        if ( ! $record->exists() ) {
     1562            return;
     1563        }
     1564
     1565        // Clear the main record cache.
     1566        wp_cache_delete( $record->get_id(), $this->get_full_name() );
     1567
     1568        // Clear any potential old cache entries by attempting to fetch and clear them
     1569        // This handles cases where cache keys have changed due to updates
     1570        $this->clear_stale_cache_entries( $record->get_id() );
     1571    }
     1572
     1573    /**
     1574     * Clears potentially stale cache entries for a record.
     1575     *
     1576     * @param int $record_id The record ID.
     1577     */
     1578    private function clear_stale_cache_entries( $record_id ) {
     1579        global $wpdb;
     1580
     1581        // For updates, we need to clear cache for old values that might have changed
     1582        if ( ! empty( $record_id ) ) {
     1583
     1584            // Get the current database values to clear any old cached entries
     1585            $table_name   = $this->get_db_table_name();
     1586            $current_data = $wpdb->get_row(
     1587                $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $record_id ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     1588                ARRAY_A
     1589            );
     1590
     1591            if ( $current_data ) {
     1592                foreach ( $this->get_cache_keys() as $key ) {
     1593                    $value = $current_data[ $key ] ?? null;
     1594                    if ( is_string( $value ) && '' !== $value ) {
     1595                        // Clear cache for database values (in case they differ from the record object)
     1596                        wp_cache_delete( $value, $this->get_prop_cache_key( $key ) );
     1597                    }
     1598                }
     1599            }
     1600        }
    15491601    }
    15501602
     
    15971649     * @param string $default The default label.
    15981650     */
    1599     public function get_label( $key, $default ) {
    1600         return isset( $this->labels[ $key ] ) ? $this->labels[ $key ] : $default;
     1651    public function get_label( $key, $default_value ) {
     1652        return $this->labels[ $key ] ?? $default_value;
    16011653    }
    16021654}
  • hizzle-downloads/tags/1.2.2/vendor/hizzle/store/src/Query.php

    r3245426 r3356445  
    306306
    307307        // Prepare aggregate fields.
    308         foreach ( $aggregate_fields as $field => $function ) {
     308        foreach ( $aggregate_fields as $field => $aggregate ) {
     309
     310            if ( ! is_array( $aggregate ) ) {
     311                $aggregate = wp_parse_list( $aggregate );
     312            }
    309313
    310314            // Handle CASE expressions
    311             if ( is_array( $function ) && isset( $function['case'] ) ) {
    312                 $case_field = $this->prefix_field( esc_sql( sanitize_key( $function['case']['field'] ) ) );
     315            if ( is_array( $aggregate ) && isset( $aggregate['case'] ) ) {
     316                $case_field = $this->prefix_field( esc_sql( sanitize_key( $aggregate['case']['field'] ) ) );
    313317                if ( empty( $case_field ) ) {
    314318                    throw new Store_Exception( 'query_invalid_field', 'Invalid case field.' );
     
    316320
    317321                $case_sql = "CASE $case_field";
    318                 foreach ( $function['case']['when'] as $when => $then ) {
    319                     $when     = esc_sql( $when );
    320                     $then_sql = $this->prepare_case_then( $then );
     322                foreach ( $aggregate['case']['when'] as $when => $then ) {
     323                    $when      = esc_sql( $when );
     324                    $then_sql  = $this->prepare_case_then( $then );
    321325                    $case_sql .= " WHEN '$when' THEN $then_sql";
    322326                }
    323327
    324                 if ( isset( $function['case']['else'] ) ) {
    325                     $else_sql = $this->prepare_case_then( $function['case']['else'] );
     328                if ( isset( $aggregate['case']['else'] ) ) {
     329                    $else_sql  = $this->prepare_case_then( $aggregate['case']['else'] );
    326330                    $case_sql .= " ELSE $else_sql";
    327331                }
    328332
    329                 $case_sql .= " END";
     333                $case_sql .= ' END';
    330334
    331335                // Handle optional aggregate function wrapper
    332                 if ( isset( $function['function'] ) ) {
    333                     $agg_function = strtoupper( $function['function'] );
     336                if ( isset( $aggregate['function'] ) ) {
     337                    $agg_function = strtoupper( $aggregate['function'] );
    334338                    if ( ! in_array( $agg_function, array( 'AVG', 'COUNT', 'MAX', 'MIN', 'SUM' ), true ) ) {
    335339                        throw new Store_Exception( 'query_invalid_function', 'Invalid aggregate function.' );
     
    339343
    340344                // Handle optional math operations
    341                 if ( isset( $function['math'] ) ) {
    342                     $math_op = $this->prepare_math_expression( $function['math'] );
     345                if ( isset( $aggregate['math'] ) ) {
     346                    $math_op  = $this->prepare_math_expression( $aggregate['math'] );
    343347                    $case_sql = "($case_sql $math_op)";
    344348                }
     
    356360            }
    357361
    358             foreach ( wp_parse_list( $function ) as $function ) {
     362            foreach ( array_filter( $aggregate ) as $function ) {
     363
     364                if ( is_array( $function ) ) {
     365                    if ( ! isset( $function['function'] ) ) {
     366                        throw new Store_Exception( 'query_invalid_function', 'Invalid aggregate function configuration.' );
     367                    }
     368
     369                    $as          = isset( $function['as'] ) ? esc_sql( sanitize_key( $function['as'] ) ) : strtolower( $function['function'] ) . '_' . $field;
     370                    $query_field = isset( $function['expression'] ) ? $this->prepare_math_expression( $function['expression'], $field ) : $table_field;
     371                    $function    = $function['function'];
     372                } else {
     373                    $as          = strtolower( $function ) . '_' . $field;
     374                    $query_field = $table_field;
     375                }
    359376
    360377                // Ensure the function is supported.
     
    364381                }
    365382
    366                 $function             = strtolower( $function );
    367                 $this->query_fields[] = "$function_upper($table_field) AS {$function}_{$field}";
     383                $this->query_fields[] = "$function_upper($query_field) AS $as";
    368384            }
    369385        }
     
    480496        }
    481497
    482         // Split the expression into parts.
    483         $parts = preg_split( '/([+\-*\/])/', $expression, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
    484 
    485         // Process each part.
     498        // Handle parentheses by processing inner expressions first
     499        while ( preg_match( '/\(([^()]+)\)/', $expression, $matches ) ) {
     500            $inner_result = $this->process_simple_expression( $matches[1] );
     501            $expression   = str_replace( $matches[0], $inner_result, $expression );
     502        }
     503
     504        // Process the remaining expression
     505        return $this->process_simple_expression( $expression );
     506    }
     507
     508    /**
     509     * Processes a simple math expression without parentheses
     510     *
     511     * @param string $expression The simple math expression
     512     * @return string
     513     */
     514    protected function process_simple_expression( $expression ) {
     515        // Enhanced regex to handle operators, negative numbers, decimals, and functions
     516        $pattern = '/([+\-*\/])|(\w+\s*\()|(\))|(-?\d*\.?\d+)|([a-zA-Z_][a-zA-Z0-9_]*)/';
     517
     518        preg_match_all( $pattern, $expression, $matches, PREG_OFFSET_CAPTURE );
     519
    486520        $processed_parts = array();
    487         foreach ( $parts as $part ) {
    488             $part = trim( $part );
    489 
    490             // If it's an operator, add it directly
    491             if ( in_array( $part, array( '+', '-', '*', '/' ), true ) ) {
    492                 $processed_parts[] = $part;
     521        $i               = 0;
     522        $total           = count( $matches[0] );
     523
     524        while ( $i < $total ) {
     525            $match = $matches[0][ $i ][0];
     526            $match = trim( $match );
     527            ++$i;
     528
     529            if ( empty( $match ) ) {
    493530                continue;
    494531            }
    495532
    496             // Numbers.
    497             if ( is_numeric( $part ) ) {
    498                 $processed_parts[] = esc_sql( (float) $part );
     533            // Operators
     534            if ( preg_match( '/^[+\-*\/]$/', $match ) ) {
     535                $processed_parts[] = $match;
    499536                continue;
    500537            }
    501538
    502             // If it's a field reference, prefix it
    503             $prefixed_field = $this->prefix_field( esc_sql( sanitize_key( $part ) ) );
    504 
    505             if ( empty( $prefixed_field ) ) {
    506                 throw new Store_Exception( 'query_invalid_field', 'Invalid field in math expression.' );
    507             }
    508 
    509             if ( ! empty( $prefixed_field ) ) {
     539            // SQL Functions (e.g., ABS, ROUND, etc.)
     540            if ( preg_match( '/^(\w+)\s*\($/', $match ) ) {
     541                $function = trim( str_replace( '(', '', $match ) );
     542
     543                // Validate allowed functions
     544                $allowed_functions = array( 'ABS', 'ROUND', 'CEIL', 'FLOOR', 'SQRT', 'POW' );
     545                if ( ! in_array( strtoupper( $function ), $allowed_functions, true ) ) {
     546                    throw new Store_Exception( 'query_invalid_function', 'Invalid function in math expression.' );
     547                }
     548
     549                $processed_parts[] = strtoupper( $function ) . '(';
     550                continue;
     551            }
     552
     553            // Closing parenthesis
     554            if ( ')' === $match ) {
     555                $processed_parts[] = ')';
     556                continue;
     557            }
     558
     559            // Numbers (including negative and decimals)
     560            if ( is_numeric( $match ) ) {
     561                $processed_parts[] = esc_sql( (float) $match );
     562                continue;
     563            }
     564
     565            // Field references
     566            if ( preg_match( '/^[a-zA-Z_][a-zA-Z0-9_]*$/', $match ) ) {
     567                $prefixed_field = $this->prefix_field( esc_sql( sanitize_key( $match ) ) );
     568
     569                if ( empty( $prefixed_field ) ) {
     570                    throw new Store_Exception( 'query_invalid_field', 'Invalid field in math expression: ' . $match );
     571                }
     572
    510573                $processed_parts[] = $prefixed_field;
    511574                continue;
    512575            }
     576
     577            // If we get here, it's an unrecognized token
     578            throw new Store_Exception( 'query_invalid_expression', 'Invalid token in math expression: ' . $match );
    513579        }
    514580
  • hizzle-downloads/tags/1.2.2/vendor/hizzle/store/src/REST_Controller.php

    r3287466 r3356445  
    7979        }
    8080
     81        $collection_params = $this->get_collection_params();
     82
    8183        // METHODS to CREATE new records, READ the entire collection, or DELETE the entire collection.
    8284        register_rest_route(
     
    9092                    'callback'            => array( $this, 'get_items' ),
    9193                    'permission_callback' => array( $this, 'get_items_permissions_check' ),
    92                     'args'                => $this->get_collection_params(),
     94                    'args'                => $collection_params,
    9395                ),
    9496
     
    108110                    'permission_callback' => array( $this, 'create_item_permissions_check' ),
    109111                    'args'                => array_merge(
    110                         $this->get_collection_params(),
     112                        array_diff_key(
     113                            $collection_params,
     114                            array(
     115                                'paged'    => true,
     116                                'per_page' => true,
     117                                'offset'   => true,
     118                                'order'    => true,
     119                                'orderby'  => true,
     120                            )
     121                        ),
    111122                        array(
    112123                            'bulk_update' => array(
     
    341352                    'permission_callback' => array( $this, 'get_items_permissions_check' ),
    342353                    'args'                => array_merge(
    343                         $this->get_collection_params(),
     354                        $collection_params,
    344355                        array(
    345356                            'aggregate'    => array(
  • hizzle-downloads/tags/1.2.2/vendor/hizzle/store/src/Record.php

    r3287466 r3356445  
    253253
    254254        try {
    255 
    256255            $this->get_collection()->delete( $this, $force_delete );
    257256            return true;
    258 
    259257        } catch ( Store_Exception $e ) {
    260258            return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
     
    511509            // If this is an enum or boolean, record the change.
    512510            if ( $object->is_boolean() || $object->is_tokens || ! empty( $object->enum ) ) {
    513 
    514511                if ( ! $this->exists() || $value !== $this->data[ $prop ] ) {
    515512                    $this->enum_transition[ $prop ] = array(
     
    603600
    604601        if ( ! is_array( $string_or_array ) ) {
    605 
    606602            if ( $strict ) {
    607603                $string_or_array = preg_split( '/,+/', $string_or_array, -1, PREG_SPLIT_NO_EMPTY );
  • hizzle-downloads/trunk/hizzle-downloads.php

    r3287466 r3356445  
    44 * Plugin URI: https://hizzle.co/download-manager/
    55 * Description: A lightweight download manager plugin.
    6  * Version: 1.2.1
     6 * Version: 1.2.2
    77 * Author: Hizzle
    88 * Author URI: https://hizzle.co
     
    2222
    2323if ( ! defined( 'HIZZLE_DOWNLOADS_VERSION' ) ) {
    24     define( 'HIZZLE_DOWNLOADS_VERSION', '1.2.1' );
     24    define( 'HIZZLE_DOWNLOADS_VERSION', '1.2.2' );
    2525}
    2626
  • hizzle-downloads/trunk/readme.txt

    r3287466 r3356445  
    11=== Simple Download Manager - Hizzle Downloads ===
    22Contributors: picocodes, mutendebrian
    3 Tags: files, downloads, digital downloads
     3Tags: files, downloads, digital downloads, download manager, restrict downloads
    44Requires at least: 4.9
    55Tested up to: 6.7
    66Requires PHP: 5.6
    7 Version: 1.2.1
    8 Stable tag: 1.2.1
     7Version: 1.2.2
     8Stable tag: 1.2.2
    99License: GPLv3
    1010License URI: https://www.gnu.org/licenses/gpl-3.0.html
    1111Donate link: https://noptin.com/products/?utm_source=wp-repo&utm_medium=donate&utm_campaign=readme
    1212
    13 Allow your website visitors to download files from your site. Optionally restrict downloads by user role, ip address, etc
     13Easily add, restrict, and track digital downloads in WordPress — protect files with passwords, user roles, IPs, or subscriber access.
    1414
    1515== Description ==
    1616
    17 This plugin allows you to:-
     17**A simple WordPress download manager for secure file sharing, access control, and download tracking — perfect for digital products.**
     18★★★★★<br>
    1819
    19 - Add unlimited downloadable files.
    20 - Optionally protect each downloadable file with a password.
    21 - Restrict downloads to specific user roles.
    22 - Restrict downloads to specific ip addresses.
    23 - Restrict downloads to specific users.
    24 - Restrict downloads to newsletter subscribers.
    25 - Track file downloads.
     20Do you need a simple yet powerful way to manage file downloads on your WordPress site? This plugin makes it easy to upload, organize, and control access to downloadable files of any type. Whether you are sharing free resources, selling digital products, or delivering private documents, this plugin gives you full control over who can download your files and when.
     21
     22With unlimited downloads, flexible restrictions, and detailed tracking, you can confidently provide files to your audience while keeping them secure.
     23
     24= Key Features =
     25
     26- **Add unlimited downloadable files** – Add and manage as many downloadable files as you need, with no limits.
     27- **Password Protection** – Protect individual files with custom passwords so only authorized users can access them.
     28- **Restrict downloads to specific user roles** – Control file access based on WordPress user roles, ensuring that only administrators, editors, subscribers, or custom roles can download.
     29- **Restrict downloads to specific IP addresses** – Restrict downloads to specific IP addresses to prevent abuse or unauthorized sharing.
     30- **Restrict downloads to specific users** – Assign downloads to specific registered users for secure, private file delivery.
     31- **Restrict downloads to newsletter subscribers** – Restrict downloads to Noptin newsletter subscriber, making it an excellent tool for lead generation.
     32- **Track file downloads** – Track every file download with detailed statistics, helping you understand how your files are being accessed.
     33- **Simple Management** – A user-friendly interface makes uploading and managing files straightforward, even for beginners.
     34
     35= Why Use This Plugin? =
     36
     37Managing downloads manually in WordPress can be difficult. Links can be shared publicly, access can’t easily be restricted, and tracking is limited. This plugin solves those problems by giving you advanced tools to:
     38
     39- Protect digital products such as **software, themes, and plugins**.
     40- Share private **PDF documents, contracts, or reports** securely with clients.
     41- Provide exclusive resources like **eBooks, whitepapers, and templates** to email subscribers.
     42- Control access to files for **membership sites and online courses**.
     43- Monitor and analyze download activity to make better business decisions.
     44
     45= Benefits for Your Website =
     46
     47By installing this plugin, you’ll be able to:
     48
     49- Grow your email list by offering subscriber-only downloads.
     50- Monetize your website by controlling access to premium resources.
     51- Increase security by preventing unauthorized downloads and link sharing.
     52- Gain insights into how your downloads are performing.
     53
     54Whether you’re a blogger, developer, marketer, educator, or business owner, this plugin gives you all the tools you need to manage file downloads effectively in WordPress.
     55
     56Take control of your downloads today and provide a seamless, secure experience for your users.
    2657
    2758== Installation ==
     
    3768= Can I see how many times each file has been downloaded? =
    3869
    39 Yes, Noptin allows you to view how many times each file has been downloaded, as well as which users have downloaded the file. This information is available in the Noptin dashboard, where you can see a list of all your downloadable files and their download stats. You can also see which users have downloaded the files, and view their information and activity in your mailing list. This can be useful for tracking the popularity of your files and understanding which users are interested in them.
     70Yes, Hizzle Downloads allows you to view how many times each file has been downloaded, as well as which users have downloaded the file. This information is available in the Hizzle Downloads dashboard, where you can see a list of all your downloadable files and their download stats. You can also see which users have downloaded the files, and view their information and activity in your mailing list. This can be useful for tracking the popularity of your files and understanding which users are interested in them.
    4071
    4172= Can I restrict downloads by user role or newsletter subscription status? =
    4273
    43 Yes, Noptin allows you to restrict downloads by user role or newsletter subscription status. This means that you can choose which users are able to download your files, based on their user role on your website or their status as a newsletter subscriber. For example, you can make a file available only to users who are registered as members on your website, or only to users who have subscribed to your newsletter. This can be useful for creating exclusive content or offers for your users, and for encouraging people to sign up for your newsletter. You can set these restrictions for each of your downloadable files in the Noptin settings.
     74Yes, Hizzle Downloads allows you to restrict downloads by user role or newsletter subscription status. This means that you can choose which users are able to download your files, based on their user role on your website or their status as a newsletter subscriber. For example, you can make a file available only to users who are registered as members on your website, or only to users who have subscribed to your newsletter. This can be useful for creating exclusive content or offers for your users, and for encouraging people to sign up for your newsletter. You can set these restrictions for each of your downloadable files in the Hizzle Downloads settings.
    4475
    4576= How do I display downloadable files? =
     
    4980= How can I get in touch? =
    5081
    51 Use the [contact form on our website](https://hizzle.co/contact-us/).
     82Use the [contact form on our website](https://hizzlewp.com/contact/).
    5283
    5384= How can I contribute? =
     
    6495
    6596== Changelog ==
     97
     98= 1.2.2 =
     99- Update composer packages.
    66100
    67101= 1.2.1 =
     
    100134
    101135= 1.0.3 =
    102 - Restrict downloads to newsletter subscribers.
     136- Restrict downloads to Noptin newsletter subscribers.
    103137- Add support for password protected downloads.
    104138- Automatically update download files when there is a GitHub release.
  • hizzle-downloads/trunk/vendor/composer/installed.json

    r3287466 r3356445  
    5454        {
    5555            "name": "hizzle/store",
    56             "version": "0.2.7",
    57             "version_normalized": "0.2.7.0",
     56            "version": "0.2.11",
     57            "version_normalized": "0.2.11.0",
    5858            "source": {
    5959                "type": "git",
    6060                "url": "https://github.com/hizzle-co/datastore.git",
    61                 "reference": "6620679060f7b64e0a417758a328173e8b5ec729"
     61                "reference": "a6e17c589443de791e9bf9eb9c2536c7401f09c2"
    6262            },
    6363            "dist": {
    6464                "type": "zip",
    65                 "url": "https://api.github.com/repos/hizzle-co/datastore/zipball/6620679060f7b64e0a417758a328173e8b5ec729",
    66                 "reference": "6620679060f7b64e0a417758a328173e8b5ec729",
     65                "url": "https://api.github.com/repos/hizzle-co/datastore/zipball/a6e17c589443de791e9bf9eb9c2536c7401f09c2",
     66                "reference": "a6e17c589443de791e9bf9eb9c2536c7401f09c2",
    6767                "shasum": ""
    6868            },
     
    7070                "php": ">=5.3.0"
    7171            },
    72             "time": "2025-05-03T10:20:52+00:00",
     72            "time": "2025-08-20T05:32:06+00:00",
    7373            "type": "library",
    7474            "installation-source": "dist",
     
    9999            "support": {
    100100                "issues": "https://github.com/hizzle-co/datastore/issues",
    101                 "source": "https://github.com/hizzle-co/datastore/tree/0.2.7"
     101                "source": "https://github.com/hizzle-co/datastore/tree/0.2.11"
    102102            },
    103103            "install-path": "../hizzle/store"
  • hizzle-downloads/trunk/vendor/composer/installed.php

    r3287466 r3356445  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => 'eacb4c22f68cba9ad5d53a20b1db45c4f10bad5a',
     6        'reference' => '54e05e6de537c0de0a2c4e916ec2394d169ccd55',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => 'eacb4c22f68cba9ad5d53a20b1db45c4f10bad5a',
     16            'reference' => '54e05e6de537c0de0a2c4e916ec2394d169ccd55',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
     
    3030        ),
    3131        'hizzle/store' => array(
    32             'pretty_version' => '0.2.7',
    33             'version' => '0.2.7.0',
    34             'reference' => '6620679060f7b64e0a417758a328173e8b5ec729',
     32            'pretty_version' => '0.2.11',
     33            'version' => '0.2.11.0',
     34            'reference' => 'a6e17c589443de791e9bf9eb9c2536c7401f09c2',
    3535            'type' => 'library',
    3636            'install_path' => __DIR__ . '/../hizzle/store',
  • hizzle-downloads/trunk/vendor/hizzle/store/example-plugin.php

    r3287466 r3356445  
    44Plugin URI: https://hizzle.co/
    55Description: Hizzle Data Stores
    6 Version: 0.2.7
     6Version: 0.2.11
    77Author: picocodes
    88Author URI: https://github.com/picocodes/
  • hizzle-downloads/trunk/vendor/hizzle/store/src/Collection.php

    r3287466 r3356445  
    427427                $schema .= "PRIMARY KEY  ($cols),\n"; // Maintain 2 spaces between key and opening bracket.
    428428            } elseif ( 'unique' === $index ) {
    429 
    430429                foreach ( $cols as $prop => $index ) {
    431430                    $schema .= "UNIQUE KEY $prop ($index),\n";
     
    727726
    728727    /**
     728     * Returns a prop's cache key.
     729     *
     730     * @param string $prop The prop to get the cache key for.
     731     * @return string The cache key.
     732     */
     733    private function get_prop_cache_key( $prop ) {
     734        $current_version = 'v2'; // Update this version when the cache key structure changes.
     735        return $this->hook_prefix( $current_version . '_ids_by_' . $prop, true );
     736    }
     737
     738    /**
    729739     * Retrieves an ID by a given prop.
    730740     *
     
    741751
    742752        // Try the cache.
    743         $value = trim( $value );
    744         $id    = wp_cache_get( $value, $this->hook_prefix( 'ids_by_' . $prop, true ) );
     753        $value     = trim( $value );
     754        $cache_key = $this->get_prop_cache_key( $prop );
     755        $id        = wp_cache_get( $value, $cache_key );
    745756
    746757        // Maybe retrieve from the db.
     
    753764
    754765            // Update the cache.
    755             wp_cache_set( $value, $id, $this->hook_prefix( 'ids_by_' . $prop, true ) );
     766            wp_cache_set( $value, $id, $cache_key, empty( $id ) ? MINUTE_IN_SECONDS : HOUR_IN_SECONDS );
    756767        }
    757768
     
    778789            // Date fields.
    779790            if ( $value instanceof Date_Time ) {
    780 
    781791                if ( ! empty( $this->props[ $key ] ) && 'date' === strtolower( $this->props[ $key ]->type ) ) {
    782792                    $value = $value->utc( 'Y-m-d' );
     
    846856        $record->apply_changes();
    847857
    848         // Clear the cache.
    849         $this->clear_cache( $record->get_data() );
     858        // Clear any stale cache entries.
     859        $this->clear_cache( $record );
    850860
    851861        // Fires after creating a record.
     
    915925                apply_filters( $this->hook_prefix( 'insert_formats', true ), $formats, $record )
    916926            );
     927
     928            if ( empty( $result ) && ! empty( $wpdb->last_error ) ) {
     929                noptin_error_log( 'Error creating record: ' . $wpdb->last_error );
     930            }
    917931
    918932            return $result ? $wpdb->insert_id : 0;
     
    10131027
    10141028            if ( $prop->is_meta_key_multiple ) {
    1015 
    10161029                $new       = (array) $new;
    10171030                $to_delete = array_diff( $current, $new );
     
    11081121                $this->save_defaults( $record );
    11091122                $raw_data = $record->get_data();
    1110 
    11111123            } elseif ( empty( $raw_data ) ) {
    11121124                $this->not_found();
     
    11241136            // Cache the record data.
    11251137            $this->update_cache( $data );
    1126 
    11271138        }
    11281139
     
    12031214        // Fires before updating a record.
    12041215        do_action( $this->hook_prefix( 'before_update', true ), $record );
     1216
     1217        // Clear cache early to prevent race conditions and stale data
     1218        // Do this before any database operations
     1219        $this->clear_cache( $record );
    12051220
    12061221        $raw_changes = array_keys( $record->get_changes() );
     
    12351250        $record->apply_changes();
    12361251
    1237         // Clear the cache.
    1238         $this->clear_cache( $record->get_data() );
    1239 
    12401252        if ( $has_changes ) {
    12411253
     
    12641276
    12651277        // Invalidate cache.
    1266         $this->clear_cache( $record->get_data() );
     1278        $this->clear_cache( $record );
    12671279
    12681280        // If this is a CPT, delete the post.
     
    15171529
    15181530        // Ensure we have an array.
    1519         if ( ! is_array( $record ) ) {
     1531        if ( ! is_array( $record ) || empty( $record['id'] ) ) {
    15201532            return;
    15211533        }
    15221534
     1535        // Cache lookup values for faster queries
    15231536        foreach ( $this->get_cache_keys() as $key ) {
    15241537            if ( isset( $record[ $key ] ) && ! empty( $record[ $key ] ) ) {
    1525                 wp_cache_set( $record[ $key ], $record['id'], $this->hook_prefix( 'ids_by_' . $key, true ), WEEK_IN_SECONDS );
     1538                wp_cache_set( $record[ $key ], $record['id'], $this->get_prop_cache_key( $key ), HOUR_IN_SECONDS );
    15261539            }
    15271540        }
    15281541
    15291542        // Cache the entire record.
    1530         wp_cache_set( $record['id'], $record, $this->get_full_name(), DAY_IN_SECONDS );
     1543        wp_cache_set( $record['id'], $record, $this->get_full_name(), HOUR_IN_SECONDS );
    15311544    }
    15321545
     
    15341547     * Clean caches.
    15351548     *
    1536      * @param object $record The raw db record.
     1549     * @param Record $record The raw db record.
    15371550     */
    15381551    public function clear_cache( $record ) {
    15391552
    1540         $record = (object) $record;
    1541 
    15421553        foreach ( $this->get_cache_keys() as $key ) {
    1543             if ( ! is_null( $record->$key ) && '' !== $record->$key ) {
    1544                 wp_cache_delete( $record->$key, $this->hook_prefix( 'ids_by_' . $key, true ) );
    1545             }
    1546         }
    1547 
    1548         wp_cache_delete( $record->id, $this->get_full_name() );
     1554            $value = $record->get( $key );
     1555            if ( is_string( $value ) && '' !== $value ) {
     1556                wp_cache_delete( $value, $this->get_prop_cache_key( $key ) );
     1557            }
     1558        }
     1559
     1560        // Bail early if the record doesn't exist.
     1561        if ( ! $record->exists() ) {
     1562            return;
     1563        }
     1564
     1565        // Clear the main record cache.
     1566        wp_cache_delete( $record->get_id(), $this->get_full_name() );
     1567
     1568        // Clear any potential old cache entries by attempting to fetch and clear them
     1569        // This handles cases where cache keys have changed due to updates
     1570        $this->clear_stale_cache_entries( $record->get_id() );
     1571    }
     1572
     1573    /**
     1574     * Clears potentially stale cache entries for a record.
     1575     *
     1576     * @param int $record_id The record ID.
     1577     */
     1578    private function clear_stale_cache_entries( $record_id ) {
     1579        global $wpdb;
     1580
     1581        // For updates, we need to clear cache for old values that might have changed
     1582        if ( ! empty( $record_id ) ) {
     1583
     1584            // Get the current database values to clear any old cached entries
     1585            $table_name   = $this->get_db_table_name();
     1586            $current_data = $wpdb->get_row(
     1587                $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $record_id ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     1588                ARRAY_A
     1589            );
     1590
     1591            if ( $current_data ) {
     1592                foreach ( $this->get_cache_keys() as $key ) {
     1593                    $value = $current_data[ $key ] ?? null;
     1594                    if ( is_string( $value ) && '' !== $value ) {
     1595                        // Clear cache for database values (in case they differ from the record object)
     1596                        wp_cache_delete( $value, $this->get_prop_cache_key( $key ) );
     1597                    }
     1598                }
     1599            }
     1600        }
    15491601    }
    15501602
     
    15971649     * @param string $default The default label.
    15981650     */
    1599     public function get_label( $key, $default ) {
    1600         return isset( $this->labels[ $key ] ) ? $this->labels[ $key ] : $default;
     1651    public function get_label( $key, $default_value ) {
     1652        return $this->labels[ $key ] ?? $default_value;
    16011653    }
    16021654}
  • hizzle-downloads/trunk/vendor/hizzle/store/src/Query.php

    r3245426 r3356445  
    306306
    307307        // Prepare aggregate fields.
    308         foreach ( $aggregate_fields as $field => $function ) {
     308        foreach ( $aggregate_fields as $field => $aggregate ) {
     309
     310            if ( ! is_array( $aggregate ) ) {
     311                $aggregate = wp_parse_list( $aggregate );
     312            }
    309313
    310314            // Handle CASE expressions
    311             if ( is_array( $function ) && isset( $function['case'] ) ) {
    312                 $case_field = $this->prefix_field( esc_sql( sanitize_key( $function['case']['field'] ) ) );
     315            if ( is_array( $aggregate ) && isset( $aggregate['case'] ) ) {
     316                $case_field = $this->prefix_field( esc_sql( sanitize_key( $aggregate['case']['field'] ) ) );
    313317                if ( empty( $case_field ) ) {
    314318                    throw new Store_Exception( 'query_invalid_field', 'Invalid case field.' );
     
    316320
    317321                $case_sql = "CASE $case_field";
    318                 foreach ( $function['case']['when'] as $when => $then ) {
    319                     $when     = esc_sql( $when );
    320                     $then_sql = $this->prepare_case_then( $then );
     322                foreach ( $aggregate['case']['when'] as $when => $then ) {
     323                    $when      = esc_sql( $when );
     324                    $then_sql  = $this->prepare_case_then( $then );
    321325                    $case_sql .= " WHEN '$when' THEN $then_sql";
    322326                }
    323327
    324                 if ( isset( $function['case']['else'] ) ) {
    325                     $else_sql = $this->prepare_case_then( $function['case']['else'] );
     328                if ( isset( $aggregate['case']['else'] ) ) {
     329                    $else_sql  = $this->prepare_case_then( $aggregate['case']['else'] );
    326330                    $case_sql .= " ELSE $else_sql";
    327331                }
    328332
    329                 $case_sql .= " END";
     333                $case_sql .= ' END';
    330334
    331335                // Handle optional aggregate function wrapper
    332                 if ( isset( $function['function'] ) ) {
    333                     $agg_function = strtoupper( $function['function'] );
     336                if ( isset( $aggregate['function'] ) ) {
     337                    $agg_function = strtoupper( $aggregate['function'] );
    334338                    if ( ! in_array( $agg_function, array( 'AVG', 'COUNT', 'MAX', 'MIN', 'SUM' ), true ) ) {
    335339                        throw new Store_Exception( 'query_invalid_function', 'Invalid aggregate function.' );
     
    339343
    340344                // Handle optional math operations
    341                 if ( isset( $function['math'] ) ) {
    342                     $math_op = $this->prepare_math_expression( $function['math'] );
     345                if ( isset( $aggregate['math'] ) ) {
     346                    $math_op  = $this->prepare_math_expression( $aggregate['math'] );
    343347                    $case_sql = "($case_sql $math_op)";
    344348                }
     
    356360            }
    357361
    358             foreach ( wp_parse_list( $function ) as $function ) {
     362            foreach ( array_filter( $aggregate ) as $function ) {
     363
     364                if ( is_array( $function ) ) {
     365                    if ( ! isset( $function['function'] ) ) {
     366                        throw new Store_Exception( 'query_invalid_function', 'Invalid aggregate function configuration.' );
     367                    }
     368
     369                    $as          = isset( $function['as'] ) ? esc_sql( sanitize_key( $function['as'] ) ) : strtolower( $function['function'] ) . '_' . $field;
     370                    $query_field = isset( $function['expression'] ) ? $this->prepare_math_expression( $function['expression'], $field ) : $table_field;
     371                    $function    = $function['function'];
     372                } else {
     373                    $as          = strtolower( $function ) . '_' . $field;
     374                    $query_field = $table_field;
     375                }
    359376
    360377                // Ensure the function is supported.
     
    364381                }
    365382
    366                 $function             = strtolower( $function );
    367                 $this->query_fields[] = "$function_upper($table_field) AS {$function}_{$field}";
     383                $this->query_fields[] = "$function_upper($query_field) AS $as";
    368384            }
    369385        }
     
    480496        }
    481497
    482         // Split the expression into parts.
    483         $parts = preg_split( '/([+\-*\/])/', $expression, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
    484 
    485         // Process each part.
     498        // Handle parentheses by processing inner expressions first
     499        while ( preg_match( '/\(([^()]+)\)/', $expression, $matches ) ) {
     500            $inner_result = $this->process_simple_expression( $matches[1] );
     501            $expression   = str_replace( $matches[0], $inner_result, $expression );
     502        }
     503
     504        // Process the remaining expression
     505        return $this->process_simple_expression( $expression );
     506    }
     507
     508    /**
     509     * Processes a simple math expression without parentheses
     510     *
     511     * @param string $expression The simple math expression
     512     * @return string
     513     */
     514    protected function process_simple_expression( $expression ) {
     515        // Enhanced regex to handle operators, negative numbers, decimals, and functions
     516        $pattern = '/([+\-*\/])|(\w+\s*\()|(\))|(-?\d*\.?\d+)|([a-zA-Z_][a-zA-Z0-9_]*)/';
     517
     518        preg_match_all( $pattern, $expression, $matches, PREG_OFFSET_CAPTURE );
     519
    486520        $processed_parts = array();
    487         foreach ( $parts as $part ) {
    488             $part = trim( $part );
    489 
    490             // If it's an operator, add it directly
    491             if ( in_array( $part, array( '+', '-', '*', '/' ), true ) ) {
    492                 $processed_parts[] = $part;
     521        $i               = 0;
     522        $total           = count( $matches[0] );
     523
     524        while ( $i < $total ) {
     525            $match = $matches[0][ $i ][0];
     526            $match = trim( $match );
     527            ++$i;
     528
     529            if ( empty( $match ) ) {
    493530                continue;
    494531            }
    495532
    496             // Numbers.
    497             if ( is_numeric( $part ) ) {
    498                 $processed_parts[] = esc_sql( (float) $part );
     533            // Operators
     534            if ( preg_match( '/^[+\-*\/]$/', $match ) ) {
     535                $processed_parts[] = $match;
    499536                continue;
    500537            }
    501538
    502             // If it's a field reference, prefix it
    503             $prefixed_field = $this->prefix_field( esc_sql( sanitize_key( $part ) ) );
    504 
    505             if ( empty( $prefixed_field ) ) {
    506                 throw new Store_Exception( 'query_invalid_field', 'Invalid field in math expression.' );
    507             }
    508 
    509             if ( ! empty( $prefixed_field ) ) {
     539            // SQL Functions (e.g., ABS, ROUND, etc.)
     540            if ( preg_match( '/^(\w+)\s*\($/', $match ) ) {
     541                $function = trim( str_replace( '(', '', $match ) );
     542
     543                // Validate allowed functions
     544                $allowed_functions = array( 'ABS', 'ROUND', 'CEIL', 'FLOOR', 'SQRT', 'POW' );
     545                if ( ! in_array( strtoupper( $function ), $allowed_functions, true ) ) {
     546                    throw new Store_Exception( 'query_invalid_function', 'Invalid function in math expression.' );
     547                }
     548
     549                $processed_parts[] = strtoupper( $function ) . '(';
     550                continue;
     551            }
     552
     553            // Closing parenthesis
     554            if ( ')' === $match ) {
     555                $processed_parts[] = ')';
     556                continue;
     557            }
     558
     559            // Numbers (including negative and decimals)
     560            if ( is_numeric( $match ) ) {
     561                $processed_parts[] = esc_sql( (float) $match );
     562                continue;
     563            }
     564
     565            // Field references
     566            if ( preg_match( '/^[a-zA-Z_][a-zA-Z0-9_]*$/', $match ) ) {
     567                $prefixed_field = $this->prefix_field( esc_sql( sanitize_key( $match ) ) );
     568
     569                if ( empty( $prefixed_field ) ) {
     570                    throw new Store_Exception( 'query_invalid_field', 'Invalid field in math expression: ' . $match );
     571                }
     572
    510573                $processed_parts[] = $prefixed_field;
    511574                continue;
    512575            }
     576
     577            // If we get here, it's an unrecognized token
     578            throw new Store_Exception( 'query_invalid_expression', 'Invalid token in math expression: ' . $match );
    513579        }
    514580
  • hizzle-downloads/trunk/vendor/hizzle/store/src/REST_Controller.php

    r3287466 r3356445  
    7979        }
    8080
     81        $collection_params = $this->get_collection_params();
     82
    8183        // METHODS to CREATE new records, READ the entire collection, or DELETE the entire collection.
    8284        register_rest_route(
     
    9092                    'callback'            => array( $this, 'get_items' ),
    9193                    'permission_callback' => array( $this, 'get_items_permissions_check' ),
    92                     'args'                => $this->get_collection_params(),
     94                    'args'                => $collection_params,
    9395                ),
    9496
     
    108110                    'permission_callback' => array( $this, 'create_item_permissions_check' ),
    109111                    'args'                => array_merge(
    110                         $this->get_collection_params(),
     112                        array_diff_key(
     113                            $collection_params,
     114                            array(
     115                                'paged'    => true,
     116                                'per_page' => true,
     117                                'offset'   => true,
     118                                'order'    => true,
     119                                'orderby'  => true,
     120                            )
     121                        ),
    111122                        array(
    112123                            'bulk_update' => array(
     
    341352                    'permission_callback' => array( $this, 'get_items_permissions_check' ),
    342353                    'args'                => array_merge(
    343                         $this->get_collection_params(),
     354                        $collection_params,
    344355                        array(
    345356                            'aggregate'    => array(
  • hizzle-downloads/trunk/vendor/hizzle/store/src/Record.php

    r3287466 r3356445  
    253253
    254254        try {
    255 
    256255            $this->get_collection()->delete( $this, $force_delete );
    257256            return true;
    258 
    259257        } catch ( Store_Exception $e ) {
    260258            return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
     
    511509            // If this is an enum or boolean, record the change.
    512510            if ( $object->is_boolean() || $object->is_tokens || ! empty( $object->enum ) ) {
    513 
    514511                if ( ! $this->exists() || $value !== $this->data[ $prop ] ) {
    515512                    $this->enum_transition[ $prop ] = array(
     
    603600
    604601        if ( ! is_array( $string_or_array ) ) {
    605 
    606602            if ( $strict ) {
    607603                $string_or_array = preg_split( '/,+/', $string_or_array, -1, PREG_SPLIT_NO_EMPTY );
Note: See TracChangeset for help on using the changeset viewer.