2018-06-04 19:21:02 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Beta Tester plugin main class
|
|
|
|
*
|
|
|
|
* @package WC_Beta_Tester
|
|
|
|
*/
|
|
|
|
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* WC_Beta_Tester Main Class.
|
|
|
|
*/
|
|
|
|
class WC_Beta_Tester {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Config
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private $config = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Plugin instance.
|
|
|
|
*
|
|
|
|
* @var WC_Beta_Tester
|
|
|
|
*/
|
|
|
|
protected static $_instance = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Main Instance.
|
|
|
|
*/
|
|
|
|
public static function instance() {
|
|
|
|
self::$_instance = is_null( self::$_instance ) ? new self() : self::$_instance;
|
|
|
|
|
|
|
|
return self::$_instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ran on activation to flush update cache
|
|
|
|
*/
|
|
|
|
public static function activate() {
|
|
|
|
delete_site_transient( 'update_plugins' );
|
|
|
|
delete_site_transient( 'woocommerce_latest_tag' );
|
|
|
|
}
|
|
|
|
|
2018-06-06 15:14:32 +00:00
|
|
|
/**
|
|
|
|
* Get plugin settings.
|
|
|
|
*
|
|
|
|
* @return object
|
|
|
|
*/
|
|
|
|
public static function get_settings() {
|
|
|
|
$settings = (object) wp_parse_args(
|
|
|
|
get_option( 'wc_beta_tester_options', array() ),
|
|
|
|
array(
|
|
|
|
'channel' => 'stable',
|
|
|
|
'auto_update' => false,
|
|
|
|
)
|
|
|
|
);
|
|
|
|
$settings->auto_update = (bool) $settings->auto_update;
|
|
|
|
return $settings;
|
|
|
|
}
|
|
|
|
|
2018-06-06 08:02:45 +00:00
|
|
|
/**
|
|
|
|
* Get the plugin url.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function plugin_url() {
|
|
|
|
return untrailingslashit( plugins_url( '/', WC_BETA_TESTER_FILE ) );
|
|
|
|
}
|
|
|
|
|
2018-06-04 19:21:02 +00:00
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*/
|
|
|
|
public function __construct() {
|
2018-06-07 12:18:29 +00:00
|
|
|
$this->plugin_name = plugin_basename( WC_BETA_TESTER_FILE );
|
|
|
|
|
2018-06-04 19:21:02 +00:00
|
|
|
$this->config = array(
|
|
|
|
'plugin_file' => 'woocommerce/woocommerce.php',
|
|
|
|
'slug' => 'woocommerce',
|
|
|
|
'proper_folder_name' => 'woocommerce',
|
2018-06-05 13:26:59 +00:00
|
|
|
'api_url' => 'https://api.wordpress.org/plugins/info/1.0/woocommerce.json',
|
2018-06-05 17:08:06 +00:00
|
|
|
'repo_url' => 'https://wordpress.org/plugins/woocommerce/',
|
2018-06-04 19:21:02 +00:00
|
|
|
'requires' => '4.4',
|
|
|
|
'tested' => '4.9',
|
|
|
|
);
|
|
|
|
|
|
|
|
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'api_check' ) );
|
|
|
|
add_filter( 'plugins_api', array( $this, 'get_plugin_info' ), 10, 3 );
|
|
|
|
add_filter( 'upgrader_source_selection', array( $this, 'upgrader_source_selection' ), 10, 3 );
|
2018-06-06 15:18:34 +00:00
|
|
|
add_filter( 'auto_update_plugin', 'auto_update_woocommerce', 100, 2 );
|
2018-06-07 08:52:33 +00:00
|
|
|
add_filter( 'plugins_api_result', array( $this, 'plugin_api_prerelease_info' ), 10, 3 );
|
2018-06-07 12:18:29 +00:00
|
|
|
add_filter( "plugin_action_links_{$this->plugin_name}", array( $this, 'plugin_action_links' ), 10, 1 );
|
2018-06-05 08:37:08 +00:00
|
|
|
|
|
|
|
$this->includes();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Include any classes we need within admin.
|
|
|
|
*/
|
|
|
|
public function includes() {
|
2018-06-05 17:06:21 +00:00
|
|
|
include_once dirname( __FILE__ ) . '/class-wc-beta-tester-admin-menus.php';
|
2018-06-06 11:29:52 +00:00
|
|
|
include_once dirname( __FILE__ ) . '/class-wc-beta-tester-admin-assets.php';
|
2018-06-04 19:21:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update args.
|
|
|
|
*/
|
|
|
|
public function set_update_args() {
|
|
|
|
$plugin_data = $this->get_plugin_data();
|
|
|
|
$this->config['plugin_name'] = $plugin_data['Name'];
|
|
|
|
$this->config['version'] = $plugin_data['Version'];
|
|
|
|
$this->config['author'] = $plugin_data['Author'];
|
|
|
|
$this->config['homepage'] = $plugin_data['PluginURI'];
|
|
|
|
$this->config['new_version'] = $this->get_latest_prerelease();
|
|
|
|
$this->config['last_updated'] = $this->get_date();
|
|
|
|
$this->config['description'] = $this->get_description();
|
2018-06-05 13:31:53 +00:00
|
|
|
$this->config['zip_url'] = $this->get_download_url( $this->config['new_version'] );
|
2018-06-04 19:21:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check wether or not the transients need to be overruled and API needs to be called for every single page load
|
|
|
|
*
|
|
|
|
* @return bool overrule or not
|
|
|
|
*/
|
|
|
|
public function overrule_transients() {
|
|
|
|
return defined( 'WC_BETA_TESTER_FORCE_UPDATE' ) && WC_BETA_TESTER_FORCE_UPDATE;
|
|
|
|
}
|
|
|
|
|
2018-06-07 08:52:33 +00:00
|
|
|
/**
|
|
|
|
* Checks if a given version is a pre-release.
|
|
|
|
*
|
|
|
|
* @param string $version
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function is_prerelease( $version ) {
|
|
|
|
return preg_match( '/(.*)?-(beta|rc)(.*)/', $version );
|
|
|
|
}
|
|
|
|
|
2018-06-04 19:21:02 +00:00
|
|
|
/**
|
2018-06-05 13:26:59 +00:00
|
|
|
* Get New Version from WPorg
|
2018-06-04 19:21:02 +00:00
|
|
|
*
|
|
|
|
* @since 1.0
|
|
|
|
* @return int $version the version number
|
|
|
|
*/
|
|
|
|
public function get_latest_prerelease() {
|
|
|
|
$tagged_version = get_site_transient( md5( $this->config['slug'] ) . '_latest_tag' );
|
|
|
|
|
|
|
|
if ( $this->overrule_transients() || empty( $tagged_version ) ) {
|
|
|
|
|
2018-06-05 21:18:07 +00:00
|
|
|
$data = $this->get_wporg_data();
|
2018-06-04 19:21:02 +00:00
|
|
|
|
2018-06-05 21:18:07 +00:00
|
|
|
$latest_version = $data->version;
|
|
|
|
$versions = (array) $data->versions;
|
2018-06-04 19:21:02 +00:00
|
|
|
|
2018-06-05 21:18:07 +00:00
|
|
|
foreach ( $versions as $version => $download_url ) {
|
2018-06-07 08:52:33 +00:00
|
|
|
if ( $this->is_prerelease( $version ) ) {
|
2018-06-05 21:18:07 +00:00
|
|
|
$tagged_version = $version;
|
2018-06-04 19:21:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Refresh every 6 hours.
|
|
|
|
if ( ! empty( $tagged_version ) ) {
|
2018-06-07 09:24:38 +00:00
|
|
|
set_site_transient( md5( $this->config['slug'] ) . '_latest_tag', $tagged_version, HOUR_IN_SECONDS * 6 );
|
2018-06-04 19:21:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $tagged_version;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-06-05 13:26:59 +00:00
|
|
|
* Get Data from .org API.
|
2018-06-04 19:21:02 +00:00
|
|
|
*
|
|
|
|
* @since 1.0
|
2018-06-05 13:26:59 +00:00
|
|
|
* @return array $wporg_data The data.
|
2018-06-04 19:21:02 +00:00
|
|
|
*/
|
2018-06-05 13:26:59 +00:00
|
|
|
public function get_wporg_data() {
|
|
|
|
if ( ! empty( $this->wporg_data ) ) {
|
|
|
|
return $this->wporg_data;
|
|
|
|
}
|
2018-06-04 19:21:02 +00:00
|
|
|
|
2018-06-05 13:26:59 +00:00
|
|
|
$wporg_data = get_site_transient( md5( $this->config['slug'] ) . '_wporg_data' );
|
2018-06-04 19:21:02 +00:00
|
|
|
|
2018-06-05 13:26:59 +00:00
|
|
|
if ( $this->overrule_transients() || ( ! isset( $wporg_data ) || ! $wporg_data || '' === $wporg_data ) ) {
|
|
|
|
$wporg_data = wp_remote_get( $this->config['api_url'] );
|
2018-06-04 19:21:02 +00:00
|
|
|
|
2018-06-05 13:26:59 +00:00
|
|
|
if ( is_wp_error( $wporg_data ) ) {
|
|
|
|
return false;
|
2018-06-04 19:21:02 +00:00
|
|
|
}
|
|
|
|
|
2018-06-05 13:26:59 +00:00
|
|
|
$wporg_data = json_decode( $wporg_data['body'] );
|
|
|
|
|
|
|
|
// Refresh every 6 hours.
|
2018-06-07 09:24:38 +00:00
|
|
|
set_site_transient( md5( $this->config['slug'] ) . '_wporg_data', $wporg_data, HOUR_IN_SECONDS * 6 );
|
2018-06-04 19:21:02 +00:00
|
|
|
}
|
|
|
|
|
2018-06-05 13:26:59 +00:00
|
|
|
// Store the data in this class instance for future calls.
|
|
|
|
$this->wporg_data = $wporg_data;
|
|
|
|
|
|
|
|
return $wporg_data;
|
2018-06-04 19:21:02 +00:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Get update date
|
|
|
|
*
|
|
|
|
* @since 1.0
|
|
|
|
* @return string $date the date
|
|
|
|
*/
|
|
|
|
public function get_date() {
|
2018-06-05 13:31:53 +00:00
|
|
|
$data = $this->get_wporg_data();
|
|
|
|
return ! empty( $data->last_updated ) ? date( 'Y-m-d', strtotime( $data->last_updated ) ) : false;
|
2018-06-04 19:21:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get plugin description
|
|
|
|
*
|
|
|
|
* @since 1.0
|
|
|
|
* @return string $description the description
|
|
|
|
*/
|
|
|
|
public function get_description() {
|
2018-06-05 13:31:53 +00:00
|
|
|
$data = $this->get_wporg_data();
|
2018-06-05 13:26:59 +00:00
|
|
|
|
2018-06-05 13:31:53 +00:00
|
|
|
if ( empty( $data->sections->description ) ) {
|
2018-06-05 13:26:59 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-06-05 13:31:53 +00:00
|
|
|
$data = $data->sections->description;
|
2018-06-05 13:26:59 +00:00
|
|
|
|
2018-06-05 17:06:21 +00:00
|
|
|
if ( preg_match( '%(<p[^>]*>.*?</p>)%i', $data, $regs ) ) {
|
2018-06-05 13:31:53 +00:00
|
|
|
$data = strip_tags( $regs[1] );
|
2018-06-05 13:26:59 +00:00
|
|
|
}
|
|
|
|
|
2018-06-05 13:31:53 +00:00
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-06-05 17:06:21 +00:00
|
|
|
* Get plugin download URL.
|
2018-06-05 13:31:53 +00:00
|
|
|
*
|
|
|
|
* @since 1.0
|
2018-06-05 17:06:21 +00:00
|
|
|
* @param string $version The version.
|
|
|
|
* @return string
|
2018-06-05 13:31:53 +00:00
|
|
|
*/
|
|
|
|
public function get_download_url( $version ) {
|
|
|
|
$data = $this->get_wporg_data();
|
|
|
|
|
|
|
|
if ( empty( $data->versions->$version ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $data->versions->$version;
|
2018-06-04 19:21:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get Plugin data.
|
|
|
|
*
|
|
|
|
* @since 1.0
|
|
|
|
* @return object $data The data.
|
|
|
|
*/
|
|
|
|
public function get_plugin_data() {
|
|
|
|
return get_plugin_data( WP_PLUGIN_DIR . '/' . $this->config['plugin_file'] );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-06-05 13:26:59 +00:00
|
|
|
* Hook into the plugin update check and connect to WPorg.
|
2018-06-04 19:21:02 +00:00
|
|
|
*
|
|
|
|
* @since 1.0
|
|
|
|
* @param object $transient The plugin data transient.
|
|
|
|
* @return object $transient Updated plugin data transient.
|
|
|
|
*/
|
|
|
|
public function api_check( $transient ) {
|
|
|
|
// Check if the transient contains the 'checked' information,
|
|
|
|
// If not, just return its value without hacking it.
|
|
|
|
if ( empty( $transient->checked ) ) {
|
|
|
|
return $transient;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear our transient.
|
|
|
|
delete_site_transient( md5( $this->config['slug'] ) . '_latest_tag' );
|
|
|
|
|
|
|
|
// Update tags.
|
|
|
|
$this->set_update_args();
|
|
|
|
|
|
|
|
// check the version and decide if it's new.
|
|
|
|
$update = version_compare( $this->config['new_version'], $this->config['version'], '>' );
|
|
|
|
|
|
|
|
if ( $update ) {
|
|
|
|
$response = new stdClass();
|
|
|
|
$response->plugin = $this->config['slug'];
|
|
|
|
$response->new_version = $this->config['new_version'];
|
|
|
|
$response->slug = $this->config['slug'];
|
2018-06-05 17:08:06 +00:00
|
|
|
$response->url = $this->config['repo_url'];
|
2018-06-04 19:21:02 +00:00
|
|
|
$response->package = $this->config['zip_url'];
|
|
|
|
|
|
|
|
// If response is false, don't alter the transient.
|
|
|
|
if ( false !== $response ) {
|
|
|
|
$transient->response[ $this->config['plugin_file'] ] = $response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $transient;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get Plugin info.
|
|
|
|
*
|
|
|
|
* @since 1.0
|
|
|
|
* @param bool $false Always false.
|
|
|
|
* @param string $action The API function being performed.
|
|
|
|
* @param object $response The plugin info.
|
|
|
|
* @return object
|
|
|
|
*/
|
|
|
|
public function get_plugin_info( $false, $action, $response ) {
|
|
|
|
// Check if this call API is for the right plugin.
|
|
|
|
if ( ! isset( $response->slug ) || $response->slug !== $this->config['slug'] ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update tags.
|
|
|
|
$this->set_update_args();
|
|
|
|
|
|
|
|
$response->slug = $this->config['slug'];
|
|
|
|
$response->plugin = $this->config['slug'];
|
|
|
|
$response->name = $this->config['plugin_name'];
|
|
|
|
$response->plugin_name = $this->config['plugin_name'];
|
|
|
|
$response->version = $this->config['new_version'];
|
|
|
|
$response->author = $this->config['author'];
|
|
|
|
$response->homepage = $this->config['homepage'];
|
|
|
|
$response->requires = $this->config['requires'];
|
|
|
|
$response->tested = $this->config['tested'];
|
|
|
|
$response->downloaded = 0;
|
|
|
|
$response->last_updated = $this->config['last_updated'];
|
|
|
|
$response->sections = array( 'description' => $this->config['description'] );
|
|
|
|
$response->download_link = $this->config['zip_url'];
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Rename the downloaded zip
|
|
|
|
*
|
|
|
|
* @param string $source File source location.
|
|
|
|
* @param string $remote_source Remote file source location.
|
|
|
|
* @param WP_Upgrader $upgrader WordPress Upgrader instance.
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function upgrader_source_selection( $source, $remote_source, $upgrader ) {
|
|
|
|
global $wp_filesystem;
|
|
|
|
|
|
|
|
if ( strstr( $source, '/woocommerce-woocommerce-' ) ) {
|
|
|
|
$corrected_source = trailingslashit( $remote_source ) . trailingslashit( $this->config['proper_folder_name'] );
|
|
|
|
|
|
|
|
if ( $wp_filesystem->move( $source, $corrected_source, true ) ) {
|
|
|
|
return $corrected_source;
|
|
|
|
} else {
|
|
|
|
return new WP_Error();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $source;
|
|
|
|
}
|
2018-06-06 08:02:45 +00:00
|
|
|
|
2018-06-07 08:52:33 +00:00
|
|
|
/**
|
|
|
|
* Add additional information to the Plugin Information version details modal.
|
|
|
|
*
|
|
|
|
* @param object|WP_Error $res Response object or WP_Error.
|
|
|
|
* @param string $action The type of information being requested from the Plugin Installation API.
|
|
|
|
* @param object $args Plugin API arguments.
|
|
|
|
* @return object|WP_Error
|
|
|
|
*/
|
|
|
|
public function plugin_api_prerelease_info( $res, $action, $args ) {
|
|
|
|
// We only care about the plugin information action for woocommerce
|
|
|
|
if ( ! isset( $args->slug ) || 'woocommerce' !== $args->slug || 'plugin_information' !== $action ) {
|
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not a pre-release, no need to do anything.
|
|
|
|
if ( ! $this->is_prerelease( $res->version ) ) {
|
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
2018-06-07 08:55:37 +00:00
|
|
|
if ( isset( $res->sections['description'] ) ) {
|
|
|
|
$res->sections['description'] = __( '<h1><span>⚠</span>This is a pre-release<span>⚠</span></h1>', 'woocommerce-beta-tester' )
|
|
|
|
. $res->sections['description'];
|
2018-06-07 08:52:33 +00:00
|
|
|
}
|
|
|
|
|
2018-06-07 10:10:11 +00:00
|
|
|
$res->sections['pre-release_information'] = make_clickable( wpautop( $this->get_version_information( $res->version ) ) );
|
2018-06-07 08:52:33 +00:00
|
|
|
$res->sections['pre-release_information'] .= sprintf(
|
|
|
|
/* translators: 1: GitHub pre-release URL */
|
|
|
|
__( '<p><a target="_blank" href="%s">Read more on GitHub</a></p>' ),
|
|
|
|
'https://github.com/woocommerce/woocommerce/releases/tag/' . $res->version
|
|
|
|
);
|
|
|
|
|
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
2018-06-06 15:18:34 +00:00
|
|
|
/**
|
|
|
|
* Enable auto updates for WooCommerce.
|
|
|
|
*
|
|
|
|
* @param bool $update Should this autoupdate.
|
|
|
|
* @param object $plugin Plugin being checked.
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function auto_update_woocommerce( $update, $plugin ) {
|
|
|
|
if ( true === $this->get_settings()->auto_update && 'woocommerce' === $item->slug ) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return $update;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-06 08:02:45 +00:00
|
|
|
/**
|
|
|
|
* Gets release information from GitHub.
|
|
|
|
*
|
2018-06-06 15:14:32 +00:00
|
|
|
* @param string $version Version number.
|
2018-06-06 12:28:24 +00:00
|
|
|
* @return bool|string False on error, description otherwise
|
2018-06-06 08:02:45 +00:00
|
|
|
*/
|
|
|
|
public function get_version_information( $version ) {
|
|
|
|
$url = 'https://api.github.com/repos/woocommerce/woocommerce/releases/tags/' . $version;
|
|
|
|
|
|
|
|
$github_data = get_site_transient( md5( $url ) . '_github_data' );
|
|
|
|
|
2018-06-06 12:28:24 +00:00
|
|
|
if ( $this->overrule_transients() || empty( $github_data ) ) {
|
2018-06-06 08:02:45 +00:00
|
|
|
$github_data = wp_remote_get( $url );
|
|
|
|
|
|
|
|
if ( is_wp_error( $github_data ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$github_data = json_decode( $github_data['body'] );
|
|
|
|
|
|
|
|
if ( empty( $github_data->body ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$github_data = $github_data->body;
|
|
|
|
|
|
|
|
// Refresh every 6 hours.
|
2018-06-06 12:28:24 +00:00
|
|
|
set_site_transient( md5( $url ) . '_github_data', $github_data, HOUR_IN_SECONDS * 6 );
|
2018-06-06 08:02:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $github_data;
|
|
|
|
}
|
2018-06-07 12:18:29 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Show action links on the plugin screen.
|
|
|
|
*
|
|
|
|
* @param mixed $links Plugin Action links.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function plugin_action_links( $links ) {
|
|
|
|
$action_links = array(
|
|
|
|
'switch-version' => sprintf(
|
|
|
|
'<a href="%s">%s</a>',
|
|
|
|
esc_url( admin_url( 'tools.php?page=wc-beta-tester-version-picker' ) ),
|
|
|
|
esc_html__( 'Switch versions', 'wp-crontrol' )
|
|
|
|
),
|
|
|
|
'settings' => sprintf(
|
|
|
|
'<a href="%s">%s</a>',
|
|
|
|
esc_url( admin_url( 'plugins.php?page=wc-beta-tester' ) ),
|
|
|
|
esc_html__( 'Settings', 'wp-crontrol' )
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
return array_merge( $action_links, $links );
|
|
|
|
}
|
2018-06-04 19:21:02 +00:00
|
|
|
}
|