Use uploads folder for writing cache files
-
The plugin installation instructions state:
2. Give read & write access to tmp folder;
However, plugin developers are discouraged from trying to write into other filesystem locations than 1) the WordPress uploads folder or 2) the system’s temporary directory, whereas the latter is only suitable for temporary files having a very short lifetime.
All plugin developers should follow this guideline, because the uploads folder is the only folder within WordPress that is (and should be) writable in all server environments. The application should not be able to write anywhere else, because that would be a security risk.
My next comment will be a patch (diff) that you can apply to the plugin code in order to fix the problem. Afterwards the plugin will save the cache files in a subdirectory of the uploads folder.
The change has no side-effects for existing sites, so it can be incorporated into the next release.
Additionally, you will then be able to remove one step from your plugin’s installation instructions, so it becomes easier to install your plugin for everyone. So it’s a win-win; for security but also for users! 🙂
-
diff --git a/wp-content/plugins/etsy-shop/etsy-shop.php b/wp-content/plugins/etsy-shop/etsy-shop.php index 6eacf67..4f289e0 100644 --- a/wp-content/plugins/etsy-shop/etsy-shop.php +++ b/wp-content/plugins/etsy-shop/etsy-shop.php @@ -264,7 +264,7 @@ function etsy_shop_shortcode( $atts ) { add_shortcode( 'etsy-shop', 'etsy_shop_shortcode' ); function etsy_shop_getShopSectionListings( $etsy_shop_id, $etsy_section_id, $language ) { - $etsy_cache_file = dirname( __FILE__ ).'/tmp/'.$etsy_shop_id.'-'.$etsy_section_id.'_cache.json'; + $etsy_cache_file = etsy_shop_get_data_dir().'/'.$etsy_shop_id.'-'.$etsy_section_id.'_cache.json'; // if no cache file exist if (!file_exists( $etsy_cache_file ) or ( time() - filemtime( $etsy_cache_file ) >= ETSY_SHOP_CACHE_LIFE ) or get_option( 'etsy_shop_debug_mode' ) ) { @@ -490,7 +490,7 @@ function etsy_shop_options_page() { // did a file was choosed? if ( isset( $_GET['file'] ) ) { - $tmp_directory = plugin_dir_path( __FILE__ ) . 'tmp/'; + $tmp_directory = etsy_shop_get_data_dir() . '/'; // REGEX for security! $filename = str_replace( '.json', '', $_GET['file'] ); @@ -622,7 +622,7 @@ function etsy_shop_options_page() { </tr> </thead> <?php - $files = glob( dirname( __FILE__ ).'/tmp/*.json' ); + $files = glob( etsy_shop_get_data_dir().'/*.json' ); $time_zone = get_option('timezone_string'); date_default_timezone_set( $time_zone ); foreach ($files as $file) { @@ -681,4 +681,12 @@ function etsy_shop_plugin_action_links( $links, $file ) { return $links; } -?> +function etsy_shop_get_data_dir() { + $uploads_config = wp_upload_dir( NULL, FALSE ); + $dir = $uploads_config['basedir'] . '/etsy-shop/tmp'; + if ( !file_exists( $dir ) ) { + wp_mkdir_p( $dir ); + } + return $dir; +} +Or even better, use WordPress’s Transient API, which has been built exactly for jobs like this, as also explained here:
https://css-tricks.com/the-deal-with-wordpress-transients/My next comment will contain a patch that replaces the file-based cache with a transient-based cache.
diff --git a/wp-content/plugins/etsy-shop/etsy-shop.php b/wp-content/plugins/etsy-shop/etsy-shop.php index 4f289e0..b44dd39 100644 --- a/wp-content/plugins/etsy-shop/etsy-shop.php +++ b/wp-content/plugins/etsy-shop/etsy-shop.php @@ -28,7 +28,6 @@ Version: 0.18 */ /* Roadmap to version 1.x - * TODO: touch() file in tmp folder * TODO: reset cache function * TODO: edit cache life * TODO: allow more than 100 items @@ -39,7 +38,7 @@ Version: 0.18 */ define( 'ETSY_SHOP_VERSION', '0.18'); -define( 'ETSY_SHOP_CACHE_LIFE', 21600 ); // 6 hours in seconds +define( 'ETSY_SHOP_CACHE_LIFE', 6 * HOUR_IN_SECONDS ); // load translation add_action( 'init', 'etsy_shop_load_translation_file' ); @@ -264,38 +263,28 @@ function etsy_shop_shortcode( $atts ) { add_shortcode( 'etsy-shop', 'etsy_shop_shortcode' ); function etsy_shop_getShopSectionListings( $etsy_shop_id, $etsy_section_id, $language ) { - $etsy_cache_file = etsy_shop_get_data_dir().'/'.$etsy_shop_id.'-'.$etsy_section_id.'_cache.json'; - - // if no cache file exist - if (!file_exists( $etsy_cache_file ) or ( time() - filemtime( $etsy_cache_file ) >= ETSY_SHOP_CACHE_LIFE ) or get_option( 'etsy_shop_debug_mode' ) ) { - // if language set - if ($language != null) { - $reponse = etsy_shop_api_request( "shops/$etsy_shop_id/sections/$etsy_section_id/listings/active", '&limit=100&includes=Images&language='.$language ); - } else { - $reponse = etsy_shop_api_request( "shops/$etsy_shop_id/sections/$etsy_section_id/listings/active", '&limit=100&includes=Images' ); + $transient_id = 'etsy-shop_' . $etsy_shop_id . '-' . $etsy_section_id; + if ($language) { + $transient_id .= '-' . $language; + } + if (get_option('etsy_shop_debug_mode') || !$response = get_transient($transient_id)) { + $api_parameters = '&limit=100&includes=Images'; + if ($language) { + $api_parameters .= '&language=' . $language; } - - if ( !is_wp_error( $reponse ) ) { - // if request OK - $tmp_file = $etsy_cache_file.rand().'.tmp'; - file_put_contents( $tmp_file, $reponse ); - rename( $tmp_file, $etsy_cache_file ); - } else { - // return WP_Error - return $reponse; + $response = etsy_shop_api_request("shops/$etsy_shop_id/sections/$etsy_section_id/listings/active", $api_parameters); + if (is_wp_error($response)) { + return $response; } - } else { - // read cache file - $reponse = file_get_contents( $etsy_cache_file ); + set_transient($transient_id, $response, ETSY_SHOP_CACHE_LIFE); } if ( get_option( 'etsy_shop_debug_mode' ) ) { - $file_content = file_get_contents( $etsy_cache_file ); - print_r( '<h3>--- Etsy Cache File:' . $etsy_cache_file . ' ---</h3>' ); - print_r( $file_content ); + print_r( '<h3>--- Etsy Transient: ' . $transient_id . ' ---</h3>' ); + print_r( $response ); } - $data = json_decode( $reponse ); + $data = json_decode( $response ); return $data; } @@ -426,11 +415,14 @@ function etsy_shop_menu() { } function etsy_shop_options_page() { + global $wpdb; + // did the user is allowed? if ( !current_user_can( 'manage_options' ) ) { wp_die( __( 'You do not have sufficient permissions to access this page.', 'etsyshop' ) ); } + $updated = FALSE; if ( isset( $_POST['submit'] ) ) { // did the user enter an API Key? if ( isset( $_POST['etsy_shop_api_key'] ) ) { @@ -485,30 +477,11 @@ function etsy_shop_options_page() { } } - // delete cache file - if ( isset( $_GET['delete'] ) ) { - - // did a file was choosed? - if ( isset( $_GET['file'] ) ) { - $tmp_directory = etsy_shop_get_data_dir() . '/'; - - // REGEX for security! - $filename = str_replace( '.json', '', $_GET['file'] ); - $filename = preg_replace( '/[^a-zA-Z0-9-_]/', '', $filename ); - - $fullpath_filename = $tmp_directory . $filename . '.json'; - if ( file_exists( $fullpath_filename ) ) { - $deletion = unlink( $fullpath_filename ); - } else { - $deletion = false; - } - - if ( $deletion ) { - // and remember to note deletion to user - $deleted = true; - $deleted_file = $fullpath_filename; - } - } + // delete cache + $deleted = FALSE; + if (isset($_GET['delete']) && isset($_GET['transient_id'])) { + delete_transient($_GET['transient_id']); + $deleted = TRUE; } // grab the Etsy API key @@ -544,7 +517,7 @@ function etsy_shop_options_page() { } if ( $deleted ) { - echo '<div class="updated fade"><p><strong>'. __( 'Cache file deleted:', 'etsyshop' ) . ' ' . $deleted_file . '</strong></p></div>'; + echo '<div class="updated fade"><p><strong>'. __( 'Cache deleted:', 'etsyshop' ) . ' ' . $_GET['transient_id'] . '</strong></p></div>'; } // print the Options Page @@ -616,29 +589,35 @@ function etsy_shop_options_page() { <thead id="EtsyShopCacheTableHead"> <tr> <th><?php _e('Shop Section', 'etsyshop'); ?></th> - <th><?php _e('Filename', 'etsyshop'); ?></th> - <th><?php _e('Last update', 'etsyshop'); ?></th> + <th><?php _e('Transient ID', 'etsyshop'); ?></th> + <th><?php _e('Expires', 'etsyshop'); ?></th> <th></th> </tr> </thead> <?php - $files = glob( etsy_shop_get_data_dir().'/*.json' ); - $time_zone = get_option('timezone_string'); - date_default_timezone_set( $time_zone ); - foreach ($files as $file) { - // downgrade to support PHP 5.2.4 - //$etsy_shop_section = explode( "-", strstr(basename( $file ), '_cache.json', true ) ); - $etsy_shop_section = explode( "-", substr( basename( $file ), 0, strpos( basename( $file ), '_cache.json' ) ) ); - $etsy_shop_section_info = etsy_shop_getShopSection($etsy_shop_section[0], $etsy_shop_section[1]); + $transients = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name LIKE '_transient_etsy-shop_%'"); + foreach ($transients as $transient) { + $transient_id = substr($transient->option_name, strlen('_transient_')); + @list($shop_name, $shop_section_id, $language) = explode('-', substr($transient_id, strlen('etsy-shop_')), 3); + $etsy_shop_section_info = etsy_shop_getShopSection($shop_name, $shop_section_id, $language); + $expiration = $wpdb->get_var("SELECT option_value FROM {$wpdb->options} WHERE option_name = '_transient_timeout_{$transient_id}'"); if ( !is_wp_error( $etsy_shop_section_info ) ) { - echo '<tr><td>' . $etsy_shop_section[0] . ' / ' . $etsy_shop_section_info->results[0]->title . '</td><td>' . basename( $file ) . '</td><td>' . date( "Y-m-d H:i:s", filemtime( $file ) ) . '</td><td><a href="options-general.php?page=etsy-shop.php&delete&file=' . basename( $file ) . '" title="'. __('Delete cache file', 'etsyshop') .'"><span class="dashicons dashicons-trash"></span></a></td></tr>'; - // echo '<tr><td>' . $etsy_shop_section[0] . ' / ' . $etsy_shop_section_info->results[0]->title . '</td><td>' . basename( $file ) . '</td><td>' . date( "Y-m-d H:i:s", filemtime( $file ) ) . '</td><td><a href="" title="' . _e('Delete cache file', 'etsyshop'); . '"><span class="dashicons dashicons-trash"></span></a></td></tr>'; + echo '<tr> +<td>' . $shop_name . ' / ' . $etsy_shop_section_info->results[0]->title . '</td> +<td>' . $transient_id . '</td> +<td>' . date_i18n( "Y-m-d H:i:s", $expiration ) . '</td> +<td><a href="options-general.php?page=etsy-shop.php&delete&transient_id=' . urlencode($transient_id) . '" title="'. __('Delete cache', 'etsyshop') .'"><span class="dashicons dashicons-trash"></span></a></td> +</tr>'; } else { - echo '<tr><td>' . $etsy_shop_section[0] . ' / <span style="color:red;">Error on API Request</span>' . '</td><td>' . basename( $file ) . '</td><td>' . date( "Y-m-d H:i:s", filemtime( $file ) ) . '</td><td></td></tr>'; + echo '<tr> +<td>' . $shop_name . ' / <span style="color:red;">Error on API Request</span>' . '</td> +<td>' . $transient_id . '</td> +<td>' . ($expiration ? date_i18n( "Y-m-d H:i:s", $expiration ) : $expiration ) . '</td> +<td></td> +</tr>'; } } ?></table><?php } else { _e('You must enter your Etsy API Key to view cache status!', 'etsyshop'); } ?> - <p class="description"><?php _e( 'You may reset cache a any time by deleting files in tmp folder of the plugin.', 'etsyshop' ); ?></p> </td> </tr> </table> @@ -681,12 +660,3 @@ function etsy_shop_plugin_action_links( $links, $file ) { return $links; } -function etsy_shop_get_data_dir() { - $uploads_config = wp_upload_dir(NULL, FALSE); - $dir = $uploads_config['basedir'] . '/etsy-shop/tmp'; - if ( !file_exists($dir) ) { - wp_mkdir_p($dir); - } - return $dir; -} -Any feedback?
The topic ‘Use uploads folder for writing cache files’ is closed to new replies.