Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default/button-zoom-out.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/template-iframe-legacy.tpl =================================================================== diff -u --- plugins/360-product-rotation/includes/template-iframe-legacy.tpl (revision 0) +++ plugins/360-product-rotation/includes/template-iframe-legacy.tpl (revision 1309) @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + +
+
+ + + + + \ No newline at end of file Index: plugins/360-product-rotation/includes/inc.constants.php =================================================================== diff -u --- plugins/360-product-rotation/includes/inc.constants.php (revision 0) +++ plugins/360-product-rotation/includes/inc.constants.php (revision 1309) @@ -0,0 +1,13 @@ +Utils()->construct_iframe_content() ); + Index: plugins/360-product-rotation/includes/yofla_3drt/templates/div_tag.tpl =================================================================== diff -u --- plugins/360-product-rotation/includes/yofla_3drt/templates/div_tag.tpl (revision 0) +++ plugins/360-product-rotation/includes/yofla_3drt/templates/div_tag.tpl (revision 1309) @@ -0,0 +1,3 @@ + +
+ \ No newline at end of file Index: plugins/360-product-rotation/includes/plugin-media-upload.php =================================================================== diff -u --- plugins/360-product-rotation/includes/plugin-media-upload.php (revision 0) +++ plugins/360-product-rotation/includes/plugin-media-upload.php (revision 1309) @@ -0,0 +1,415 @@ + 0 ) +{ + die( implode(' ', $status_errors)); +} +else +{ + die(''); +} + + +/** + * Checks validity of upload, extracts zip archive and moves it to correct destionation + * + * @return array + */ +function process_upload() +{ + //errors + $errors = array(); + + /** + * Uplaod received + */ + if(isset($_FILES["FileInput"]) && $_FILES["FileInput"]["error"]== UPLOAD_ERR_OK) + { + $upload_dir = wp_upload_dir(); + $products_folder = trailingslashit($upload_dir['basedir']).'yofla360/'; + + //check if this is an ajax request + if (!isset($_SERVER['HTTP_X_REQUESTED_WITH'])) + { + die(); + } + + //allowed file type Server side check + switch(strtolower($_FILES['FileInput']['type'])) + { + //allowed file types + case 'application/x-zip-compressed': + case 'application/zip': + /* + case 'image/png': + case 'image/gif': + case 'image/jpeg': + case 'image/pjpeg': + case 'text/plain': + case 'text/html': //html file + case 'application/pdf': + case 'video/mp4': + */ + break; + default: + die('Unsupported File!'); //output error + } + + $file_name_all = strtolower($_FILES['FileInput']['name']); + $file_name = basename($file_name_all,'.zip'); //filename without extension + $File_Ext = substr($file_name, strrpos($file_name, '.')); //get file extention + $Random_Number = rand(0, 9999999999); //Random number to be added to name. + $zip_filename = $Random_Number.$File_Ext; //new file name + $temp_directory = trailingslashit($products_folder.'temp'.$Random_Number); + $uploaded_zip_path = $products_folder.$zip_filename; + + if(@move_uploaded_file($_FILES['FileInput']['tmp_name'], $uploaded_zip_path)) + { + // do other stuff + $zip = new ZipArchive; + $res = $zip->open($uploaded_zip_path); + + if ($res === TRUE) + { + $zip->extractTo($temp_directory); + $zip->close(); + + $status = copy_source_to_destination($temp_directory,$products_folder,$file_name,$errors); + + if(!$status) + { + $errors[] = 'Failed to move/rename the unzipped folder.'; + if( !delete_directory($temp_directory) ) + { + $errors[] = 'Failed to delete the temp. directory: '.htmlspecialchars($temp_directory); + } + } + } + else + { + $errors[] = 'Failed to unzip uploaded file.'; + } + + unlink($uploaded_zip_path); + } + else + { + $errors[] = 'Error moving uploaded File!'; + } + } + else + { + $errors[] = 'Something wrong with upload! Is "upload_max_filesize" set correctly?'; + } + + return $errors; +} + + +/** + * Checks the structure of the extracted zip archive to determine + * from where to move the extracted zip file to where. + * + * Uploaded archive can have a structure like this: + * + * 1) just images with no sub folders -> make up destination folder, move + * 2) a folder with just images -> move folder to uploads/yofla360 + * 3) just 3DRT output files -> make up destination folder,move + * 4) a folder with 3DRT output files -> move folder to uploads/yofla360 + * + * @param $path Is the (temp) path to the extracted zip directory + * @param $products_folder + * @param $file_name String the file name of the uploaded .zip archive, without extension + * @param $errors + * @return bool + */ +function copy_source_to_destination($path,$products_folder,$file_name,&$errors) +{ + + // map directories and files in extracted archive + $directories = array(); + $files = array(); + $files_backslashes = array(); //uncorrect created zip file + + if ( $handle = opendir($path) ) + { + while (false !== ($file = readdir($handle))) + { + if ($file != "." && $file != "..") + { + $file_path = $path.'/'.$file; + if (is_dir($file_path)) + { + $directories[] = $file; + } + else + { + $files[] = $file; + + //detect wrong zip file structure + if( strpos($file,'\\') > 0) + { + $files_backslashes[] = $file; + } + + } + } + } + closedir($handle); + } + else + { + $errors[] = "Failed to open the directory ".htmlspecialchars($path )." for reading!"; + return false; + } + + + //detect unallowed extensions + $files_excluded = getExcludedFiles($path); + if(sizeof($files_excluded) > 0) + { + $errors[] = "Archive must not contain executable files!"; + return false; + } + + //zip contains just images and no subfolders + if( sizeof($directories) == 0 && sizeof($files) > 0) + { + + //wrong zip file structure - folder names are encoded in file names, like: 'szow1\index.html' + if(sizeof($files_backslashes) == sizeof($files)) + { + $result = process_incorrect_zip($path, $files_backslashes, $products_folder); + delete_directory($path); //in other cases the dir is moved... + return $result; + } + // zip contains just images and no sub folders + else + { + //make up new folder name + $dir_name = makeup_folder_name($file_name); + + $source = $path; + + $destination = YoFLA360()->Utils()->get_safe_destination($products_folder.$dir_name); + + if( mkdir($destination) ) + { + + $destination_final = trailingslashit($destination).'images'; + + //rename temp dir + return rename_directory( $source, $destination_final, $errors ); + } + else + { + $errors[] = "Failed to create the directory: ".htmlspecialchars( $destination ).""; + return false; + } + } + } + //zip contains folders and images (3drt output with no containing folder) + elseif( sizeof($directories) > 0 && sizeof($files) > 0 ) + { + //make up new folder name + $dir_name = makeup_folder_name($file_name); + + $source = $path; + $destination = YoFLA360()->Utils()->get_safe_destination($products_folder.$dir_name); + + //rename temp dir + return rename_directory( $source, $destination, $errors ); + } + //archive contains one directory (images or 3drt output) + elseif(sizeof($directories == 1)) + { + //get the name of the directory in the extracted zip archive + $dir_name = $directories[0]; + + //determine folder name + $source = $path.$dir_name; + $destination = YoFLA360()->Utils()->get_safe_destination($products_folder.$dir_name); + + //move dir + $status = rename_directory( $source, $destination, $errors ); + + //remove now empty temp dir + if( $status ) + { + if ( !rmdir($path) ) $errors[] = 'Failed to delete the (empty) temp. directory: '.htmlspecialchars($path); + } + return $status; + } + else{ + if( !delete_directory($path) ) { $errors[] = 'Failed to delete the temp. directory: '.htmlspecialchars($path); } + $errors[] = 'Could not identify the directory structure of the uploaded .zip file!'; + return false; + } +} + +/** + * Renames a directory, logs error. + * + * @param $source + * @param $destination + * @param $errors + * @return bool + */ +function rename_directory ( $source, $destination, &$errors ) +{ + + if(rename($source, $destination)) + { + return true; + } + else + { + $errors[] = "Failed to rename temp directory (".htmlspecialchars($source).") to destination directory (".htmlspecialchars($destination).")"; + return false; + } +} + +/** + * Delete a dir recursively + * + * @param $dir + * @return bool + */ +function delete_directory($dir) +{ + $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); + $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); + foreach($files as $file) { + if ($file->isDir()){ + rmdir($file->getRealPath()); + } else { + unlink($file->getRealPath()); + } + } + return rmdir($dir); +} + +/** + * Returns a name of the folder where the 360 rotation will be placed. + * + * If zip archive name is provided, use that, otherwise return random name + * + * @param null $file_name + * @return bool + */ +function makeup_folder_name($file_name = null) +{ + if($file_name) + { + $file_name = preg_replace('/\s+/', '_', $file_name); + } + return ($file_name) ? $file_name : uniqid('360view_'); +} + + +function process_incorrect_zip($temp_path, $files_list, $products_folder) +{ + + //check if folder already exists + + + // loop files + foreach($files_list as $kf=>$file){ + + // get dirs along path + $file_dirs = explode('\\',$file); + + //get file name + $file_name = array_pop($file_dirs); + + //store path with directories + $file_path = $products_folder; + + //append path with fil edirectories + for($i=0; $i $value){ + $path = realpath($dir.DIRECTORY_SEPARATOR.$value); + if(!is_dir($path)) { + $results[] = $path; + } else if($value != "." && $value != "..") { + getDirContents($path, $results); + $results[] = $path; + } + } + + return $results; +} + +/** + * Returns list of files, that must not be in archive + * + * @param $path + * @return array + */ +function getExcludedFiles($path) +{ + $files = getDirContents($path); + $extensions_excluded = array('php','php3','py','sh'); + $files_excluded = array(); + + foreach($files as $k=>$file){ + $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + if(in_array($extension,$extensions_excluded)) + { + $files_excluded[] = $file; + } + } + + return $files_excluded; +} Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default/zoomslider-handle.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default/button-right.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/pure-white/button-right.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/pure-white/button-start.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/inc.wordpress.php =================================================================== diff -u --- plugins/360-product-rotation/includes/inc.wordpress.php (revision 0) +++ plugins/360-product-rotation/includes/inc.wordpress.php (revision 1309) @@ -0,0 +1,34 @@ +y360_options = get_option( 'yofla_360_options' ); //read options stored in WP options database + $this->_init_defaults(); + } + + /** + * Initializes the product view data based on the shortcode attributes + * + * @param $attributes + */ + public function process_attributes($attributes) + { + //set iframe styles based on user settings + if( isset( $attributes['name'] ) ) + $this->name = $attributes['name']; + + //set iframe styles based on user settings + if( isset( $attributes['iframe_styles'] ) ) + $this->iframe_styles = $attributes['iframe_styles']; + + //set styles based on user settings + if( isset( $attributes['styles'] ) ) + $this->user_styles = $attributes['styles']; + + //set ga_enabled based on user settings + if( isset($attributes['ga_enabled']) ) + $this->ga_enabled = (filter_var($attributes['ga_enabled'],FILTER_VALIDATE_BOOLEAN)) ; + + //set ga_tracking_id based on user settings + if( isset($attributes['ga_tracking_id']) ) + $this->ga_tracking_id = $attributes['ga_tracking_id']; + + //use iframe or not + if( isset($attributes['iframe']) ) + $this->iframe = filter_var( $attributes['iframe'], FILTER_VALIDATE_BOOLEAN); + + // + if( isset($attributes['ga_category']) ) + $this->ga_category = $attributes['ga_category']; + + //set google analytics label + if( isset($attributes['ga_label']) ) + $this->_ga_label = $attributes['ga_label']; + + + //set width, height + if ( isset($attributes['width']) ) + $this->width = YoFLA360()->Utils()->format_size_for_styles( $attributes['width'] ); + + if ( isset($attributes['height']) ) + $this->height = YoFLA360()->Utils()->format_size_for_styles( $attributes['height'] ); + + + //set src parameter + if ( isset($attributes['src']) ) + $this->set_src( sanitize_text_field($attributes['src']) ); + + } + + /** + * Initializes the product view data based on get aparameters + * + */ + public function process_get_parameters() + { + //set ga_enabled based on user settings + if( isset($_GET['ga_enabled']) ) + $this->ga_enabled = (filter_var($_GET['ga_enabled'],FILTER_VALIDATE_BOOLEAN)) ; + + //set ga_tracking_id based on user settings + if( isset($_GET['ga_tracking_id']) ) + $this->ga_tracking_id = $_GET['ga_tracking_id']; + + //use iframe or not + if( isset($_GET['iframe']) ) + $this->iframe = filter_var( $_GET['iframe'], FILTER_VALIDATE_BOOLEAN); + + // + if( isset($_GET['ga_category']) ) + $this->ga_category = $_GET['ga_category']; + + //set google analytics label + if( isset($_GET['ga_label']) ) + $this->_ga_label = $_GET['ga_label']; + + //set google analytics label + if( isset($_GET['ga_label']) ) + $this->_ga_label = $_GET['ga_label']; + + //src parameter + if( isset($_GET['src']) ) + $this->set_src(sanitize_text_field($_GET['src'])); + } + + /** + * Returns the url of an iframe, if view is embedded as iframe + * + * @return mixed + */ + public function get_iframe_url() + { + if(!isset($this->_iframe_url)) + { + $this->_iframe_url = YoFLA360()->Utils()->get_iframe_url($this); + } + return $this->_iframe_url; + } + + /** + * Returns url to product + * + * @return string + */ + public function get_product_url() + { + if(!isset($this->_product_url)) + { + $this->_product_url = YoFLA360()->Utils()->get_product_url($this); + } + + return $this->_product_url; + } + + /** + * Returns the url of the rotatetool js engine + * + * @return string + */ + public function get_rotatetool_js_src() + { + + //rotatetool_js from local location, if presnt and enabled + if ($this->local_engine) { + $local_engine_path = YoFLA360()->Utils()->get_full_product_path( $this->src).'rotatetool.js'; + if(file_exists($local_engine_path)){ + return $this->get_product_url().'rotatetool.js'; + } + } + + if(!empty($y360_options['rotatetooljs_url']) && filter_var($y360_options['rotatetooljs_url'], FILTER_VALIDATE_URL)){ + return $this->y360_options['rotatetooljs_url']; + } + + if(!empty($this->y360_options['license_id'])) + return YOFLA_PLAYER_URL.'?id='.$this->y360_options['license_id']; + + if (isset($_GET['license_id'])) { + return YOFLA_PLAYER_URL . '?id=' . $_GET['license_id']; + } + + return $this->_rotatetool_js_src; + } + + + + /** + * Styles used when embedding as a div (no iframe) + * + * @return string + */ + public function get_styles() + { + $format = "width: %s; height: %s; %s"; + return sprintf($format, $this->width, $this->height, $this->user_styles); + } + + /** + * Returns google analytics label if set, otherwise a default label + * + * @return string + */ + public function get_ga_label() + { + if ( empty( $this->_ga_label ) ) + { + $format = "360 @ %s"; + return sprintf($format, $this->src); + } + else + { + return $this->_ga_label; + } + } + + + /** + * Sets the src parameter (as in the embed src parameter) + * + * @param $path + */ + public function set_src($path) + { + $this->src = $path; + + if( !isset ( $this->id ) ) + { + $this->id = 'yofla360_'.str_replace('/','_',trim(YoFLA360()->Utils()->get_relative_product_path($path),'/') ); + } + if ( YoFLA360()->Utils()->is_created_with_desktop_application( $path ) ) + { + $this->_product_url = YoFLA360()->Utils()->get_uploads_url().trim($this->src,'/').'/'; + } + elseif ( YoFLA360()->Utils()->is_url_path($path) ) + { + $this->_product_url = YoFLA360()->Utils()->get_product_url($this); + } + else + { + $this->_product_url = ''; + // generate or get configFile parameter and themeUrl parameter + $this->config_url = YoFLA360()->Utils()->get_config_url($this); + $this->theme_url = YoFLA360()->Utils()->get_theme_url($this); + + } + } + + /** + * Default settings for embedding a 360 view + */ + private function _init_defaults() + { + $this->width = '100%'; + $this->height = '400px'; + $this->name = '360 Product View'; + $this->iframe = true; + $this->iframe_styles = 'max-width: 100%; border: 1px solid silver;'; + $this->user_styles = 'border: 1px solid silver;'; + $this->ga_category = 'YOFLA_360'; + $this->config_url = ''; + $this->theme_url = ''; + $this->local_engine = false; + + if(!empty($this->y360_options['iframe_styles'])) $this->iframe_styles = $this->y360_options['iframe_styles']; + if(!empty($this->y360_options['ga_enabled'])) $this->ga_enabled = $this->y360_options['ga_enabled']; + if(!empty($this->y360_options['ga_tracking_id'])) $this->ga_tracking_id = $this->y360_options['ga_tracking_id']; + if(!empty($this->y360_options['local_engine'])) $this->local_engine = $this->y360_options['local_engine']; + + //default cache setting + if( current_user_can('editor') || current_user_can('administrator') ) + { + $this->is_cache_enabled = false; //no chache for logged in users + } + else + { + $this->is_cache_enabled = true; + } + + + $this->_rotatetool_js_src = YOFLA_PLAYER_URL; + + } + + +}//class Index: plugins/360-product-rotation/includes/yofla_3drt/lib/yofla/Rotate_Utils.php =================================================================== diff -u --- plugins/360-product-rotation/includes/yofla_3drt/lib/yofla/Rotate_Utils.php (revision 0) +++ plugins/360-product-rotation/includes/yofla_3drt/lib/yofla/Rotate_Utils.php (revision 1309) @@ -0,0 +1,87 @@ +_init_config(); + } + + + /** + * Returns config in JSON format + * + */ + public function get_config_json() + { + $this->_make_config(); + $json_config = json_encode($this->_config); + $output = 'var RotationData = '.$json_config.';'.PHP_EOL; + + if($this->product_id) + { + $output .= 'var RotationData_'.$this->product_id.' = RotationData;'.PHP_EOL; + } + + return $output; + + } + + /** + * Steps required to create/modify config values + * + */ + private function _make_config() + { + $this->_use_settings(); + $this->_add_images_to_config(); + }//make_config + + + /** + * Initializes config variable with minimum default settings + * + * + */ + private function _init_config() + { + $this->_config = array(); + + //settings + $this->_config["settings"] = array( + "control"=>array( + "maxZoom"=>300, + "dragSpeed"=>0.5, + "reverseDrag"=>false, + ), + "userInterface"=>array( + "showArrows"=>true, + "showToggleFullscreenButton"=>false, + "showZoombar"=>false, + "showTogglePlayButton"=>true, + ), + "preloader"=>array( + "color1"=>'#FF000', + "type"=>'wave', + ), + "rotation"=>array( + "rotatePeriod"=>3, + "rotateDirection"=>1, + "bounce"=>false, + "rotate"=>'true', + ), + ); + + //custom + $this->_config["custom"] = array(); + + //hotspots + $this->_config["hotspots"] = array(); + + //images + $this->_config["images"] = array(); + + }//_init_config() + + /** + * Adds the images in the list to the _config array + */ + private function _add_images_to_config() + { + //sort images + $this->_sort_images(); + + //add images to config + for ($i=0; $iimages_list["images"]); $i++) + { + $this->_add_image_to_config($this->_create_image_info($i)); + } + + }//add_images_to_config + + + /** + * Modifies the settings _config array with the provided settings + */ + private function _use_settings() + { + //use defaults from "global" settings.ini + if(isset($this->settings)) + { + if(isset($this->settings["config"])) + { + $config = $this->settings["config"]; + $this->_use_settings_config($config); + } + } + + //used if images folder has own settings.ini + if (isset($this->images_list["settings"])) + { + if (isset($this->images_list["settings"]["config"])) + { + $config = $this->images_list["settings"]["config"]; + $this->_use_settings_config($config); + } + } + } + + /** + * Converts the provided default or directory level settings into + * a more structured format suitable for _config + * + * @param $config + */ + private function _use_settings_config($config) + { + + + //custom logo + if (isset($config["logo_imageUrl"])){ + $this->_config["custom"]["logo"] = array(); + $this->_config["custom"]["logo"]["imageUrl"] = $config["logo_imageUrl"]; + + if (isset($config["logo_linkUrl"])) $this->_config["custom"]["logo"]["linkUrl"] = $config["logo_linkUrl"]; + if (isset($config["logo_linkTarget"])) $this->_config["custom"]["logo"]["linkTarget"] = $config["logo_linkTarget"]; + + if (isset($config["logo_imagePositionLeft"]) && isset($config["logo_imagePositionTop"])){ + $this->_config["custom"]["logo"]["imagePosition"] = array(); + $this->_config["custom"]["logo"]["imagePosition"]["top"] = intval($config["logo_imagePositionTop"]); + $this->_config["custom"]["logo"]["imagePosition"]["left"] = intval($config["logo_imagePositionLeft"]); + } + } + + //rotation + if (isset($config["bounce"])) $this->_config["settings"]["rotation"]["bounce"] = ($config["bounce"] == TRUE); + if (isset($config["rotate"])) $this->_config["settings"]["rotation"]["rotate"] = $config["rotate"]; + if (isset($config["rotatePeriod"])) $this->_config["settings"]["rotation"]["rotatePeriod"] = floatval($config["rotatePeriod"]); + if (isset($config["rotateDirection"])) $this->_config["settings"]["rotation"]["rotateDirection"] = intval($config["rotateDirection"]); + + + //rotation multi level + if (isset($config["multilevel_verticalSteps"])){ + $this->_config["settings"]["rotation"]["multilevel"] = array(); + $this->_config["settings"]["rotation"]["multilevel"]["verticalSteps"] = max(1,intval($config["multilevel_verticalSteps"])); + + if (isset($config["multilevel_horizontalSteps"])){ + $horizontalSteps = intval($config["multilevel_horizontalSteps"]); + } + else{ + $horizontalSteps = sizeof($this->images_list["images"]) / $this->_config["settings"]["rotation"]["multilevel"]["verticalSteps"]; + $horizontalSteps = intval($horizontalSteps); + } + $this->_config["settings"]["rotation"]["multilevel"]["horizontalSteps"] = $horizontalSteps; + } + + //control + if (isset($config["maxZoom"])) $this->_config["settings"]["control"]["maxZoom"] = intval($config["maxZoom"]); + if (isset($config["maxZoomAuto"])) $this->_config["settings"]["control"]["maxZoom"] = ($config["maxZoomAuto"] == TRUE); + if (isset($config["dragSpeed"])) $this->_config["settings"]["control"]["dragSpeed"] = floatval($config["dragSpeed"]); + if (isset($config["reverseDrag"])) $this->_config["settings"]["control"]["reverseDrag"] = ($config["reverseDrag"] == TRUE); + if (isset($config["enableSwing"])) $this->_config["settings"]["control"]["enableSwing"] = ($config["enableSwing"] == TRUE); + if (isset($config["rotateOnMouseHover"])) $this->_config["settings"]["control"]["rotateOnMouseHover"] = ($config["rotateOnMouseHover"] == TRUE); + if (isset($config["clickUrl"])) $this->_config["settings"]["control"]["clickUrl"] = $config["clickUrl"]; + if (isset($config["clickUrlTarget"])) $this->_config["settings"]["control"]["clickUrlTarget"] = $config["clickUrlTarget"]; + if (isset($config["disableMouseControl"])) $this->_config["settings"]["control"]["disableMouseControl"] = ($config["disableMouseControl"] == TRUE); + if (isset($config["switchToPanOnYmovement"])) $this->_config["settings"]["control"]["switchToPanOnYmovement"] = ($config["switchToPanOnYmovement"] == TRUE); + if (isset($config["mouseWheelZooms"])) $this->_config["settings"]["control"]["mouseWheelZooms"] = ($config["mouseWheelZooms"] == TRUE); + + //preloader + if (isset($config["color1"])) $this->_config["settings"]["preloader"]["color1"] = $config["color1"]; + if (isset($config["type"])) $this->_config["settings"]["preloader"]["type"] = $config["type"]; + if (isset($config["image"])) $this->_config["settings"]["preloader"]["image"] = $config["image"]; + if (isset($config["showStartButton"])) $this->_config["settings"]["preloader"]["showStartButton"] = $config["showStartButton"]; + if (isset($config["largeImagesPreloader"])) $this->_config["settings"]["preloader"]["largeImagesPreloader"] = $config["largeImagesPreloader"]; + + //userinterface + if (isset($config["showToggleFullscreenButton"])) $this->_config["settings"]["userInterface"]["showToggleFullscreenButton"] = ($config["showToggleFullscreenButton"] == TRUE); + if (isset($config["showZoombar"])) $this->_config["settings"]["userInterface"]["showZoombar"] = ($config["showZoombar"] == TRUE); + if (isset($config["showZoomButtons"])) $this->_config["settings"]["userInterface"]["showZoomButtons"] = ($config["showZoomButtons"] == TRUE); + if (isset($config["showArrows"])) $this->_config["settings"]["userInterface"]["showArrows"] = ($config["showArrows"] == TRUE); + if (isset($config["showTogglePlayButton"])) $this->_config["settings"]["userInterface"]["showTogglePlayButton"] = ($config["showTogglePlayButton"] == TRUE); + if (isset($config["showToggleRotateButton"])) $this->_config["settings"]["userInterface"]["showToggleRotateButton"] = ($config["showToggleRotateButton"] == TRUE); + if (isset($config["showMouseWheelToolTip"])) $this->_config["settings"]["userInterface"]["showMouseWheelToolTip"] = ($config["showMouseWheelToolTip"] == TRUE); + } + + + /** + * Sorts provided images (before they are written to config) + */ + private function _sort_images() + { + if (is_array($this->images_list["images"])) usort($this->images_list["images"],array($this,"cmp")); + if (is_array($this->images_list["imageslarge"])) usort($this->images_list["imageslarge"],array($this,"cmp")); + } + + private function cmp($a,$b) + { + return strnatcmp(basename($a), basename($b)); + } + + + /** + * Creates the images entry (in array format) in the exported config + * + * @param $image_id + * @return array + */ + private function _create_image_info($image_id) + { + $image_info = array(); + + $image_path_normal = $this->images_list["images"][$image_id]; + $image_path_large = (isset($this->images_list["imageslarge"])) ? $this->images_list["imageslarge"][$image_id] : NULL; + + $src = $this->_get_image_url($image_path_normal); + $image_info["src"] = $src; + + //add info on large image, if defined + if($image_path_large) + { + $image_info["srcLarge"] = $this->_get_image_url($image_path_large); + } + + return $image_info; + } + + /** + * Adds image to list in exported config + * + * @param $image_info + */ + private function _add_image_to_config($image_info) + { + $this->_config["images"][] = $image_info; + } + + /** + * The path for images in the exported config must be valigenerating config.jss, adding comments + * + * @param $image_path + * @return string + */ + private function _get_image_url($image_path) + { + $path_relative = substr($image_path,strlen($this->products_path)); + $url = $this->products_url.$path_relative; + return $url; + } + +}//class \ No newline at end of file Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-black/button-fullscreen.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-black/button-rotate.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-black/button-down.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/class-yofla360-shortcodes.php =================================================================== diff -u --- plugins/360-product-rotation/includes/class-yofla360-shortcodes.php (revision 0) +++ plugins/360-product-rotation/includes/class-yofla360-shortcodes.php (revision 1309) @@ -0,0 +1,58 @@ +yofla_360_settings = array(); //var to store plugin settings + } + + /** + * Function that processes the shortcode and outputs html code based on + * shortcode parameters. + * parameters + * + * @param $attributes + * @return string + */ + public function yofla_360_shortcode_callback($attributes) { + + //setup view data + $viewData = new YoFLA360ViewData(); + $viewData->process_attributes($attributes); + + //EXIT if src attribute is not set + if(!isset( $viewData->src )) + { + YoFLA360()->add_error('src attribute not specified!'); + return YoFLA360()->Frontend()->format_plugin_output( YoFLA360()->get_errors() ); + } + + //embedding using an iframe + if( $viewData->iframe ) + { + $code = YoFLA360()->Frontend()->get_embed_iframe_html($viewData); + } + //embedding using div + else + { + //embed script + wp_register_script( 'yofla_360_rotatetool_js', $viewData->get_rotatetool_js_src(), false, '1.0.0' ); + wp_enqueue_script( 'yofla_360_rotatetool_js' ); + + //get code + $code = YoFLA360()->Frontend()->get_embed_div_element_html($viewData); + } + + // send html to browser + return YoFLA360()->Frontend()->format_plugin_output($code); + } + +}//class Index: plugins/360-product-rotation/includes/yofla_3drt/themes/pure-white/button-up.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/lib/yofla/Rotate_Tool.php =================================================================== diff -u --- plugins/360-product-rotation/includes/yofla_3drt/lib/yofla/Rotate_Tool.php (revision 0) +++ plugins/360-product-rotation/includes/yofla_3drt/lib/yofla/Rotate_Tool.php (revision 1309) @@ -0,0 +1,1340 @@ + + * + * Support: http://www.yofla.com/3d-rotate/contact + * + * @author Matus Laco, www.yofla.com, matus@yofla.com + * @version 0.2.6 + * @since May 2014 + * @copyright Matus Laco, www.yofla.com + * @license GPLv2 for WordPress Plugin + * @license Commerical license needed for other usages + * + * Prerequisites: + * 1) Product Images + * Place product photos into a directory within the "products folder", + * e.g. "products/backpack". + * + * It is possible to use a separate set of high-resolution images. Then use these folder names + * - "products/backpack/images" for normal images (e.g. 400x300) + * - "products/backpack/imageslarge" for hi-res images (e.g. 1024x768) + * + * Full documentation: + * https://www.yofla.com/3d-rotate/ + * + * + */ + +include_once "Rotate_Config.php"; +include_once "Rotate_Utils.php"; + +class Rotate_Tool +{ + const SCRIPT_VERSION = '0.2.6'; + const SYSTEM_DIRECTORY_NAME = 'yofla_3drt'; //the directory where the templates,lib,player_files subdirs are + const PRODUCTS_DIRECTORY_NAME = 'products'; + const IMAGES_DIRECTORY = 'images'; + const IMAGES_DIRECTORY_LARGE = 'imageslarge'; + const ASSETS_DIRECTORY = 'assets'; + + public static $system_path; //full system path to "system directory" + public static $cache_path; //full system path to "cache directory" + public static $system_url; //url of the "system directory" + public static $products_path; //full system path to "products directory" + public static $products_url; //url to products directory + public static $cache_url; //url to cache directory + public static $settings; //array of defaults from settings.ini + public static $errors = array(); //for error logging + public static $products_list = array(); //list of available products + public static $is_cache_disabled = false; + public static $rotatetool_js_src = false; + + private static $_is_initialized = false; + private static $_reserved_directory_names = array( + self::IMAGES_DIRECTORY, + self::IMAGES_DIRECTORY_LARGE, + self::ASSETS_DIRECTORY); + private static $_products_csv_path = null; //full system path to products csv + + private static $_options = array(); + + public static function disable_cache() + { + self::$is_cache_disabled = true; + } + + + /** + * Initializes the Class with defaults + */ + private static function _initialize() + { + //set the full system path to the system directory + self::_check_system_path(); + + //set web root + self::set_system_url(); + + //set default cache dirs + self::set_cache_path(); + + //set default cache url + self::set_cache_url(); + + //load settings + self::_load_settings(); + + //set paths to products directory + self::_check_products_path(); + + //check cache folders + self::_check_cache_directories(); + + //set vars + self::$_products_csv_path = self::$cache_path."products.dat"; + + //set initialized + self::$_is_initialized = true; + } + + + /** + * Checks if sytem_directory is set, if not, uses the calee script path + */ + private static function _check_system_path() + { + //set system path if it is not already set + if (self::$system_path === NULL) + { + $path = dirname(__FILE__); //get system path to current file + $pos = strpos($path,'/lib/yofla'); //get position of the lib subdirectory system directory + if($pos) $path = substr($path,0,$pos); //remove path after /lib subdirectory, this ensures we have the "system path" + self::set_system_path($path); + } + } + + /** + * Checks if full system path to products directory is set + */ + private static function _check_products_path() + { + if(self::$system_path === NULL) return FALSE; + + if (!self::$products_path) + { + self::$products_path = self::$system_path.self::PRODUCTS_DIRECTORY_NAME."/"; + } + + if (!self::$products_url) + { + self::$products_url = self::$system_url.self::PRODUCTS_DIRECTORY_NAME."/"; + } + } + + /** + * if cache folder is deleted, try to recreate it + */ + private static function _check_cache_directories() + { + + if ( !isset( self::$cache_path ) ) + { + self::$cache_path = self::$system_path.'cache/'; + } + + $cache_dir_paths = array(); + $cache_dir_paths[] = self::$cache_path; + $cache_dir_paths[] = self::$cache_path.'configs'; + $cache_dir_paths[] = self::$cache_path.'pages'; + + foreach($cache_dir_paths as $path) + { + if (!file_exists($path)) + { + //todo check if sucessfull + @mkdir($path, 0777, true); + } + } + } + + /** + * Loads the script settings from the settings.ini file + */ + private static function _load_settings() + { + + //settings.ini in products folder takes precedense + $ini_file = self::$products_path.'settings.ini'; + if(!file_exists($ini_file)) $ini_file = self::$system_path.'settings.ini'; + + self::$settings = parse_ini_file($ini_file,true); + + //inject class variable setting, if set + if(self::$rotatetool_js_src !== false) + { + self::$settings['system']['rotatetoolUrl'] = self::$rotatetool_js_src; + } + + //set products path, if set + if(isset(self::$settings['system']['productsPath'])) + { + self::set_products_path(self::$settings['system']['productsPath']); + } + + //set products url, if set + if(isset(self::$settings['system']['productsUrl'])) + { + self::set_products_url(self::$settings['system']['productsUrl']); + } + } + + /** + * Check whether the class initialization has run + */ + private static function _check_initialized() + { + if (self::$_is_initialized === false) + { + self::_initialize(); + } + //return false if intialization failed + return self::$system_path !== NULL; + } + + /** + * Sets the url path to the "system directory" + * + * @param null $url + */ + public static function set_system_url($url = null) + { + //do not override system_url if set before + if($url == null && isset(self::$system_url)) $url = self::$system_url; + + if (is_null($url)) + { + //assume no url rewrite is in place and the url is the same as the physical location of the files + $path_raw = substr(dirname(__FILE__), strlen($_SERVER[ 'DOCUMENT_ROOT' ])); //get path after domain, to current script + $pos = strpos($path_raw,'/lib/yofla'); //get position where the "system directory ends" + $path = substr($path_raw,0,$pos); //remove the part after "system directory" + $path= ltrim ($path, '/'); + $system_url = (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . '/'.$path; + } + else + { + //url is set from parameter + $system_url = $url; + } + + //set value + self::$system_url = Rotate_Tool::_add_trailing_slash($system_url); + } + + /** + * Sets the url path to the "products directory" + * + * @param null $url + */ + public static function set_products_url($url = null) + { + if (is_null($url)) + { + //no action + } + else + { + //set value + self::$products_url = Rotate_Tool::_add_trailing_slash($url); + } + } + + /** + * Sets the url path to the "cache directory" + * + * @param string $url + */ + public static function set_cache_url($url = '') + { + if ( $url == '') + { + //set default value if not already set + if ( !isset(self::$cache_url) ) + { + self::$cache_url = Rotate_Tool::_add_trailing_slash(self::$system_url.'cache'); + } + } + else + { + //set value + self::$cache_url = Rotate_Tool::_add_trailing_slash($url); + } + } + + /** + * Sets the full system path to the main yofla_3drt directory + * + * @param $path + */ + public static function set_system_path($path = null) + { + + //do not overwrite system_path if set before + if($path == null && isset(self::$system_path)) $path = self::$system_path; + + if(is_dir($path)) + { + if (function_exists('realpath') AND @realpath($path) !== FALSE) + { + self::$system_path = Rotate_Tool::_add_trailing_slash(realpath($path)); + } + else + { + self::$system_path = Rotate_Tool::_add_trailing_slash(path); + } + } + else + { + self::$errors[] = 'Provided path "'.$path.'" for yofla_3drt system directory is not valid!'."\n"; + } + } + + /** + * Sets the full system path to the products directory + * + * @param $path + */ + public static function set_products_path($path = null) + { + + //do not overwrite products_path if set before + if($path == null && isset(self::$products_path)) $path = self::$products_path; + + if(is_dir($path)) + { + if (function_exists('realpath') AND @realpath($path) !== FALSE) + { + self::$products_path = Rotate_Tool::_add_trailing_slash(realpath($path)); + } + else + { + self::$products_path = Rotate_Tool::_add_trailing_slash(path); + } + } + else + { + self::$errors[] = 'Provided path "'.$path.'" for yofla_3drt path directory is not valid!'."\n"; + } + } + + /** + * Sets the full system path to the cache directory + * + * @param $path + */ + public static function set_cache_path($path = null) + { + if($path) + { + self::$cache_path = self::_add_trailing_slash($path); + } + self::_check_cache_directories(); + } + + /** + * Returns an array with products, structure for each array entry: + * dir_name + * path + * iframe_url + * config_url + */ + public static function get_products_list() + { + //check if paths are set + if (self::_check_initialized() === FALSE) return FALSE; + + if (file_exists(self::$_products_csv_path)) + { + $products_list = Rotate_Utils::file_to_array(self::$_products_csv_path); + return $products_list; + } + else + { + self::scan(); + return self::$products_list; + } + } + + + /** + * Scans the products directory for products, + * generates config.js for each directory found (and stores it in cache) + * generates iframe content html page for each product (and stores it in cache) + * generates and stores a map of products and config.js files (in cache/products.csv) + * + * saves map to class products_list variable + * + * When running this function, all cached configs,pages, products.dat is regenerated + * + * @return array|bool + */ + public static function scan() + { + //check if paths are set + if (self::_check_initialized() === FALSE) return FALSE; + + //scan directories recursively + self::_scan_directory(self::$products_path); + + //save to cache + Rotate_Utils::array_to_file(self::$products_list,self::$_products_csv_path); + + + return (self::$products_list); + }//function scan + + /** + * Scans directory and it's subdirectories for product images (and optional settings.ini file) + * and stores the result in the class variable Rotate_Tool::$products_list as array. + * + * @param $path + * @param $settings + */ + private static function _scan_directory($path,$settings = NULL) + { + //fix path + $path = self::_add_trailing_slash($path); + + //set default settings + if($settings === NULL) $settings = self::$settings; + //get local settings for this directory + $settings_local = self::_get_directory_settings($path); + + //merge settings + if($settings_local) $settings = Rotate_Utils::merge_settings($settings, $settings_local); + + //read dir + $dir = dir($path); + + //loop dir contest + while(false !== ($entry = $dir->read())) { + //skip hidden files + if($entry[0] == ".") continue; + //construct path + $filename = $path.$entry; + //process dirs + if(is_dir($filename)) + { + //exclude reserved directories + if (in_array($entry,self::$_reserved_directory_names) == FALSE) + { + self::_scan_directory($filename,$settings); + self::_add_directory_to_products_list($filename,$settings); + } + } + } + + } + + + /** + * If provided path is a valid products directory, it + * 1) generates config.js ( gets stored in cache) + * 2) adds entry in class variable products_list + * + * @param string $path The full system path + * @param $settings The parent settings + */ + private static function _add_directory_to_products_list($path,$settings = null) + { + + if(self::_has_directory_product_images($path)) + { + + $path_relative = self::_get_relative_path_from_full_path($path); + $dir_name = basename($path_relative); + + //force creating cached config + self::get_config_file_content($path,$settings); + //force creating cached iframe pages + self::get_page_for_iframe($path_relative); + + $iframe_url = self::get_iframe_url($path_relative); + $config_url = self::get_cached_config_url($path_relative); + + //get local settings for product info or other info + $settings_local = self::_get_directory_settings($path); + + $name = (isset($settings_local["product"]["name"])) ? $settings_local["product"]["name"] : $dir_name; + + + $entry = array( + "dir_name" => $dir_name, + "path" => $path_relative, + "iframe_url" => $iframe_url, + "config_url" => $config_url, + "product_name" => $name, + ); + + //add to products list + self::$products_list[] = $entry; + + } + } + + + /** + * Returns iframe embed code with product rotation + * + * @param string $path The relative path to the directory with images (relative to products folder) + * @param array $options The options as associative array to create the iframe and rotation with. + * @return string The iframe code with working rotation + */ + public static function get_iframe($path,$options=null) + { + //check if class initialized + if (self::_check_initialized() === FALSE) return '3DRT initialization failed'; + + //store previous options + self::_store_options(); + + //update cache setting, if set + if(isset($options['cache']) && $options['cache'] === false) self::$is_cache_disabled = true; + + //check and set the products path + $full_path = self::_getSystemPathToProduct($path,$options); + if (!file_exists($full_path)) return 'Error: Path "'.$path.'" does not exist!'; + + //set products url, if set + if(isset($options['system']['productsUrl'])) self::$products_url = self::_add_trailing_slash($options['system']['productsUrl']); + + //get settings + $settings_product = self::get_cascading_settings_for_directory($full_path); + + + //consturct iframe url - important step! + $iframe_url = self::get_iframe_url($path,$options); + + + $width = $settings_product["player"]["width"]; + $height = $settings_product["player"]["height"]; + $iframe_style = $settings_product["player"]["iframeStyle"]; + + //override width/height, if set + if(isset($options["width"])) $width = intval($options["width"]); + if(isset($options["height"])) $height = intval($options["height"]); + + $iframe = array(); + $iframe["width"] = "{$width}px"; + $iframe["height"] = "{$height}px"; + $iframe["src"] = $iframe_url; + + $values = array(); + $values["{script_version}"] = self::SCRIPT_VERSION; + $values["{name}"] = "3drt-iframe"; + $values["{width}"] = $iframe["width"]; + $values["{height}"] = $iframe["height"]; + $values["{src}"] = $iframe["src"]; + $values["{class}"] = "yofla_360_iframe"; + $values["{style}"] = $iframe_style; + + //get iframe embed code + $content = self::_get_template_output('iframe_tag.tpl',$values); + + //restore settings + self::_restore_options(); + + //return content + return $content; + } + + /** + * Returns the url of the html page that is used inside of the iframe embed code as src parameter + * + * @param $path + * @param null $options Are the optional options, that override the settings specified in settings ini + * @return string + */ + public static function get_iframe_url($path,$options = null) + { + //check if paths are set + if (self::_check_initialized() === FALSE) return ''; + + $is_cache_disabled = (isset($options['cache']) && $options['cache'] == false) || self::$is_cache_disabled; + + $path_to_cached_page = self::_get_cached_iframe_page_system_path($path); + + if (file_exists($path_to_cached_page) && $is_cache_disabled == false) + { + //no action, file exists and cache is enabled + } + else //generate cached iframe page + { + //get settings + if($options) $settings = Rotate_Utils::merge_settings(self::$settings,$options); + + self::get_page_for_iframe($path,$settings); + } + + //return url + return self::get_cached_iframe_page_url($path); + + }//end get_iframe_url + + + /** + * Returns embed code as div (not as iframe) + * + * @param string $path The relative path to the directory with images (relative to products folder) + * @param array $options The options as associative array to construct the embed code + * @return string The iframe code with working rotation + */ + public static function get_div_embed($path,$options=null) + { + + //check if class initialized + if (self::_check_initialized() === FALSE) return '3DRT initialization failed'; + + //store previous options + self::_store_options(); + + //update cache setting, if set + if(isset($options['cache']) && $options['cache'] === false) self::$is_cache_disabled = true; + + //check and set the products path + $full_path = self::_add_trailing_slash(self::_getSystemPathToProduct($path,$options)); + if (!file_exists($full_path)) return 'Error: Path "'.$path.'" does not exist!'; + + //set products url, if set + if(isset($options['system']['productsUrl'])) self::$products_url = self::_add_trailing_slash($options['system']['productsUrl']); + + + //template data + $values = array(); + $values["{script_version}"] = self::SCRIPT_VERSION; + $values["{config_file}"] = self::get_config_url_for_product($path,$options); + $values["{theme_url}"] = self::_get_theme_url($options); + $values["{style}"] = ($options['style']) ? $options['style'] : ''; + $values["{divId}"] = ($options['id']) ? $options['id'] : 'yofla360_'; + $values["{class}"] = ($options['class']) ? $options['class'] : ''; + $values["{path}"] = ""; + + //check if config.js is present (3drt setup utility uploaded content) + if(file_exists($full_path.'config.js')){ + $values["{path}"] = self::$products_url.$path; + $values["{config_file}"] = ""; //reset if path is set + if(!isset($settings["system"]["theme"])){ + $values["{theme_url}"] = ""; //reset if path if not set custom theme url in options + } + } + + //get iframe embed code + $content = self::_get_template_output('div_tag.tpl',$values); + + //restore settings + self::_restore_options(); + + //return content + return $content.PHP_EOL; + } + + /** + * Returns the location + * + * @return string + */ + public static function get_rotatetooljs_url() + { + //check if class initialized + if (self::_check_initialized() === FALSE) return '3DRT initialization failed'; + return self::_get_rotatetool_url(); + } + + + private static function _get_cached_iframe_page_system_path($path) + { + $filename = self::_get_cached_iframe_page_filename($path); + $path = self::$cache_path."pages/".$filename; + return $path; + } + + private static function _get_cached_iframe_page_filename($path) + { + $filename = str_replace("/","_",$path)."_iframe.html"; + return $filename; + } + + /** + * Returns the url of cached iframe.html for given product + * + * @param $path string The relative path to the products directory + * @return string + */ + public static function get_cached_iframe_page_url($path) + { + $filename = self::_get_cached_iframe_page_filename($path); + $url = self::$cache_url."pages/".$filename; + return $url; + } + + /** + * Returns the html content of the page, that is hosted within the + * iframe embed code. Stores it in cache also. + * + * @param string $path The relative path to main products folder + * @param array $settings + * @return string + */ + public static function get_page_for_iframe($path,$settings=NULL) + { + //check if paths are set + if (self::_check_initialized() === FALSE) return ''; + + //set settings + $settings = ($settings === NULL) ? self::$settings : $settings; + + $rotatetool_url = self::_get_rotatetool_url($settings); + $theme_url = self::_get_theme_url($settings); + + $config_url = self::get_config_url_for_product($path,$settings); + + $values = array(); + $values["{generator}"] = '3D Rotate Tool :: PHP Script by YoFLA.com, Version: '.self::SCRIPT_VERSION; + $values["{rotatetool.js}"] = $rotatetool_url; + $values["{config_file}"] = $config_url; + $values["{theme_url}"] = $theme_url; + $values["{title}"] = '360 product view'; + $values["{path}"] = ""; + $values["{ga_data}"] = "{}"; + + if(isset($settings["ga_enabled"]) && isset ($settings['ga_tracking_id'] )) + { + $ga_data_format = '{"isEnabled":"true", "trackingId":"%s", "label":"%s", "category":"%s"}'; + $values["{ga_data}"] = sprintf($ga_data_format,$settings["ga_tracking_id"], $settings["ga_label"],$settings["ga_category"]); + } + + if( isset($settings['product_name']) ) + { + $values["{title}"] = $settings['product_name']; + } + + //check if config.js is present (3drt setup utility uploaded content) + //and ajdust variables + $full_path = self::_add_trailing_slash(self::_getSystemPathToProduct($path,$settings)); + if(file_exists($full_path.'config.js')){ + $values["{path}"] = self::$products_url.$path; + $values["{config_file}"] = ""; //reset if path is set + if(!isset($settings["system"]["theme"])){ + $values["{theme_url}"] = ""; //reset if path if not set custom theme url in options + } + } + + $content = self::_get_template_output('page_for_iframe.tpl',$values); + + //store content to cache + $cache_filename = self::_get_cached_iframe_page_system_path($path); + Rotate_Utils::write_file($cache_filename,$content); + + return $content; + } + + + public static function serve_page_for_iframe() + { + //check if paths are set + if (self::_check_initialized() === FALSE) return ''; + + //check if GET path parameter is set + if (self::_check_path_parameter() === FALSE) + { + self::_serve_error("Path parameter not set!"); + return; + } + + //get path + $path = urldecode($_GET["p"]); + + //todo write serve function + die(self::get_page_for_iframe($path)); + } + + + + /** + * Generates config file and provides url for that file. If cache is on, + * returns cached file, if exists. + * + * @param $path Is relative path to the products folder + * @param null $settings + * @return string + * @throws Exception + */ + public static function get_config_url_for_product($path,$settings=null) + { + + //check if paths are set + if (self::_check_initialized() === FALSE) return ''; + + //full path to product + $full_path = Rotate_Tool::_add_trailing_slash(self::$products_path.$path); + + //check if config.js was provided in the folder + if(file_exists($full_path.'config.js')) + { + $url = self::$products_url.Rotate_Tool::_add_trailing_slash($path).'config.js'; + return $url; + } + + //check cache + $cached_config_path_full = self::_get_cached_config_path_full($path); + + //serve cached version + if(file_exists($cached_config_path_full) && self::$is_cache_disabled === false) + { + //url for cached version of previously generated config.js + return self::get_cached_config_url($path); + } + //force creating cached config js + else + { + + if($settings) + { + $settings_product = $settings; + } + else{ + //get settings + $settings_product = self::get_cascading_settings_for_directory($full_path); + } + + //create (and cache) config file content + $config_content = self::get_config_file_content($full_path,$settings_product); + + if(!$config_content || strlen($config_content) < 10) + { + $error = "Errors: "; + $error .= implode('|',self::$errors); + throw new Exception($error); + } + + //return cached url + return self::get_cached_config_url($path); + } + } + + /** + * Returns the url of cached config.js for given product + * + * @param $path string The relative path to the products directory + * @return string + */ + public static function get_cached_config_url($path) + { + $url = self::$cache_url.'configs/'.self::_get_cached_config_filename($path); + return $url; + } + + + + /** + * Called externally from get_config_file.php + * + */ + public static function generate_config_file() + { + //check if paths are set + if (self::_check_initialized() === FALSE) + { + self::_serve_error("Initialization Failed!"); + return; + } + + //check if GET path parameter is set + if (self::_check_path_parameter() === FALSE) + { + self::_serve_error("Path parameter not set!"); + return; + } + + //get path parameter + $relative_path = urldecode($_GET["p"]); + + //construct system path to directory + $path = self::$products_path.$relative_path; + + // get (and cache) content of configFile + $config_content = self::get_config_file_content($path,self::get_cascading_settings_for_directory($path)); + + //error when fetching images list + if($config_content === FALSE) + { + self::_serve_error("No images found in: $path"); + return; + } + + + // serve file + self::_serve_file($config_content,'application/javascript'); + } + + /** + * Returns the content of the config.js file, based on provided path to + * product directory with images, stores also to cache + * + * @param string $path The full system path to product directory + * @param null $settings The parent settings to inherit from + * @return bool|string + */ + public static function get_config_file_content($path,$settings = NULL) + { + + //check if paths are set + if (self::_check_initialized() === FALSE) { + self::$errors[] = "get_config_file_content() : error in _check_initialized(): $path"; + return FALSE; + } + + + //get list of images + $images_list = self::get_images_list($path); + + + //error when fetching images list + if($images_list === FALSE) + { + self::$errors[] = "get_config_file_content() : error fetching image list using path: $path"; + return FALSE; + } + + $path_relative = self::_get_relative_path_from_full_path($path); + + //create config file + $config = new Rotate_Config(); + + //set paths + $config->products_path = self::$products_path; + $config->products_url = self::$products_url; + + $config->product_id = 'yofla360_'.preg_replace('/[^a-z0-9]/i','_',trim($path_relative,'/')); + + //set images + $config->images_list = $images_list; + + //set settings default settings, if provided settings as parameter are not set + $config->settings = ($settings === NULL) ? self::$settings : $settings; + + //get json representation of the config file + $output = $config->get_config_json(); + + + //cache config + $path_relative = self::_get_relative_path_from_full_path($path); + $config_full_path = self::_get_cached_config_path_full($path_relative); //tag + Rotate_Utils::write_file($config_full_path,$output); + + return $output; + } + + + /** + * Returns full system path to the location (inclusive filename) of the cached + * config.js file for given product. + * + * @param $path string The relative path of the product, relative to the products directory + * @return string + */ + private static function _get_cached_config_path_full($path) + { + $filename = self::_get_cached_config_filename($path); + $full_path = self::$cache_path.'configs/'.$filename; + return $full_path; + } + + + /** + * Returns the cached config.js filename, that is crated based on the relative path + * + * @param $path string The relative path for the product, relative to the products directory + * @return string + */ + private static function _get_cached_config_filename($path) + { + $path = Rotate_Tool::_add_trailing_slash($path); + $filename = str_replace('/','_',$path).'_config.js'; + return $filename; + } + + + + /** + * Returns an associative array of images (imageslarge) in given directory. + * Return format array("images"=>array(),"imageslarge"=>array(), "settings"=>array()). + * + * If given directory does not have the images,imageslarge subdirectories, + * the give directory is scanned + * + * Returns also parsed settings.ini file, if found in directory + * + * @param $path Is the absolute system path to the products directory + * @return array + */ + public static function get_images_list($path) + { + + //init return value + $images_list = array("images"=>NULL,"imageslarge"=>NULL,"settings"=>NULL); + + //exit if param is not a dir + if(!is_dir($path)) + { + self::$errors[] = "get_images_list() : provided path ($path) is not a valid directory!"; + return FALSE; + } + + //add trailing slash to path if missing + $path = Rotate_Tool::_add_trailing_slash($path); + + //check if images directory exists + $images_directory = $path."images"; + if(is_dir($images_directory)) $images_list["images"] = self::get_images_in_directory($images_directory); + + //check if images large directory exists + $imageslarge_directory = $path."imageslarge"; + if(is_dir($imageslarge_directory)) $images_list["imageslarge"] = self::get_images_in_directory($imageslarge_directory); + + //get images list in directory + if ($images_list["images"] === NULL) + { + $images_list["images"] = self::get_images_in_directory($path); + } + + //check settings.ini file + $images_list["settings"] = self::_get_directory_settings($path); + + return $images_list; + } + + /** + * Returns an array of images in given directory + * + * @param $path + * @return array|bool + */ + public static function get_images_in_directory($path) + { + //validate parameter + if (!is_dir($path)) return NULL; + + //add trailing slash to path if missing + $path = Rotate_Tool::_add_trailing_slash($path); + + //init vars + $result = array(); + + //read dir + $dir = dir($path); + //loop dir contest + while(false !== ($entry = $dir->read())) { + //skip hidden files + if($entry[0] == ".") continue; + $filename = "$path$entry"; + //skip dirs + if(is_dir($filename)) continue; + //add image + if(self::is_file_supported_image($filename)) $result[] = $filename; + } + + //return + return $result; + } + + /** + * Checks if file is a supported image. Supported extensions : jpg,jpeg,gif,png,bmp + * + * @param $path + * @return bool + */ + public static function is_file_supported_image($path) + { + $supported_extensions = array('jpg','jpeg','gif','png','bmp'); + if(is_file($path)){ + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + return in_array( $extension, $supported_extensions); + } + else + { + return FALSE; + } + } + + /** + * Returns a template string with replaced template variables + * + * @param $template_filename + * @param $values + * @return string + */ + private static function _get_template_output($template_filename,$values) + { + + //construct path + $template_file_path = self::$system_path.'templates/'.$template_filename; + + //check template file path + if(is_file($template_file_path) === FALSE) + { + self::$errors[] = 'Template File '.$template_file_path.' not found'."\n"; + return ''; + } + + //load template + $template_string = file_get_contents($template_file_path); + + //modify template + $new_html = strtr($template_string,$values); + + //return + return $new_html; + } + + + /** + * Adds trailing slash to provided string/path + * + * @param $path + * @return string + */ + private static function _add_trailing_slash($path) + { + //add trailing slash to path if missing + if(substr($path, -1) != "/") $path .= "/"; + return $path; + } + + + /** + * Checks if script is called with correct get parameter + * + * @return bool + */ + private static function _check_path_parameter() + { + if(isset($_GET["p"])) + { + return TRUE; + } + else + { + return FALSE; + } + } + + /** + * Checks if provided direcotry contains product images + * + * @param $path + * @return bool + */ + private static function _has_directory_product_images($path) + { + $images_list = self::get_images_list($path); + + if($images_list && isset($images_list["images"]) && sizeof($images_list["images"]) > 0 ) + { + return TRUE; + } + else + { + return FALSE; + } + } + + /** + * Returns parsed settings.ini file in given directory, if it exists + * + * @param $path + * @return array|bool + */ + private static function _get_directory_settings($path) + { + $path_fixed = self::_add_trailing_slash($path); + $path_settings = $path_fixed.'settings.ini'; + if(is_file($path_settings)) + { + $settings = parse_ini_file($path_settings,TRUE); + return $settings; + } + else + { + return NULL; + } + } + + /** + * Returns relative path to products directory from full system path + * + * @param $path + * @return string + */ + private static function _get_relative_path_from_full_path($path) + { + return substr($path,strlen(self::$products_path)); + } + + + /** + * Scan all parent directories until main system directory for settings.ini and construct + * directory-specific settings array based on settings.ini files found along the path + * + * @param string $path The full system path + * @return array + */ + public static function get_cascading_settings_for_directory($path) + { + + //path relative to products directory + $relative_path = self::_get_relative_path_from_full_path($path); + + //get directories on path as array + $dirs_array = explode("/",$relative_path); + + //default settings + $actual_settings = self::$settings; + + for ($i=0; $iget_iframe_url(); + + if($iframe_url) + { + + $html = ''; + } + else + { + YoFLA360()->add_error('Error constructing iframe url.'); + $html = YoFLA360()->get_errors(); + } + + return $html; + } + + /** + * Outputs the div embed code for integrating a 360 view into a webpage + * + * @param $viewData YoFLA360Viewdata + * @return string + */ + public function get_embed_div_element_html($viewData) + { + $template_string = '
'; + + $values = array( + '{id}' => $viewData->id, + '{width}' => $viewData->width, + '{height}' => $viewData->height, + '{styles}' => $viewData->get_styles(), + '{path}' => $viewData->get_product_url(), + '{ga_enabled}' => ($viewData->ga_enabled) ? 'true' : 'false', + '{ga_tracking_id}' => $viewData->ga_tracking_id, + '{ga_label}' => $viewData->get_ga_label(), + '{ga_category}' => $viewData->ga_category, + '{config_file}' => $viewData->config_url, + '{theme_url}' => $viewData->theme_url, + ); + + $new_html = strtr($template_string,$values); + + return $new_html; + } + + /** + * Wraps the provided string in html comments with information about hte plugin + * + * @param $html_code + * @return string + */ + public function format_plugin_output($html_code) + { + //start html output + $html_code_start = "\n".''."\n"; + $html_code_end = "\n".''."\n"; + $output = $html_code_start.$html_code.$html_code_end; + return $output; + } + + + /** + * Adds html code to error message that is returned to page + * + * @param $msg + * @return string + */ + public function format_error($msg){ + $str = '
360 Plugin Error:
'.PHP_EOL.$msg.PHP_EOL.'
'; + return $str; + } + +}//class Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-black/zoomslider-handle.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/class-yofla360-woocommerce.php =================================================================== diff -u --- plugins/360-product-rotation/includes/class-yofla360-woocommerce.php (revision 0) +++ plugins/360-product-rotation/includes/class-yofla360-woocommerce.php (revision 1309) @@ -0,0 +1,209 @@ +360° View'; + } + + + /** + * HTML of the 360 Product Tab Content + * + */ + public function yofla_360_tab_options() + { + echo ' +
+
+ '; + + //list of 360 views + $products_list = $this->_get_products_list(); + $upload_url = admin_url('upload.php?page=yofla-360-media'); + + //no product uploaded yet + if( sizeof($products_list) == 1 ) + { + echo 'No 360° views found. Do you wish to add one now?'; + } + //a product is uploaded + else + { + + if( $variants = $this->_is_variable_product() ) + { + foreach ($variants as $variant){ + + $variant_name = $variant["name"]; + $variant_id = $variant["id"]; + + echo "$variant_name"; + + woocommerce_wp_select(array( + "id" => "_y360path_variant_".$variant_id, + "name" => "_y360path_variants[$variant_id]", + "label" => "Please choose a 360° view", + "placeholder" => "", + "desc_tip" => true, + "description" => sprintf("Upload new 360 views in Media page."), + "options" => $products_list + )); + } + } + else + { + woocommerce_wp_select(array( + "id" => "_y360path", + "name" => "_y360path", + "label" => "Please choose a 360° view", + "placeholder" => "", + "desc_tip" => true, + "description" => sprintf("Upload new 360 views in Media page."), + "options" => $products_list + )); + } + } + + echo "
"; + } + + /** + * Saving path parameter when product is saved/updated + * + * @param $post_id + */ + public function yofla_360_save_product($post_id) + { + if (isset($_POST["_y360path"])) + { + update_post_meta($post_id, "_y360path", sanitize_text_field($_POST["_y360path"])); + } + elseif( isset ($_POST["_y360path_variants"]) ){ + update_post_meta($post_id, "_y360path_variants", $_POST["_y360path_variants"] ); + } + } + + + /** + * Callback function for the filter that modifies the output for the product image + * + * @param $content + * @return string + */ + public function yofla_360_replace_product_image($content) + { + global $post; + + if (empty($post->ID)) return ($content); + + $_y360path = (get_post_meta($post->ID, "_y360path", true)); + if (empty($_y360path) ) return ($content); + + $content = $this->_update_product_image($_y360path); + + return $content; + } + + + /** + * Returns the html code of the 360 view that is used instead of the main product image + * + * @param $_y360path + * @return string + */ + private function _update_product_image($_y360path) + { + $viewData = new YoFLA360ViewData(); + $viewData->set_src($_y360path); + $viewData->width = '100%'; + $viewData->height = '300px'; + + //iframe embedding + $content = YoFLA360()->Frontend()->get_embed_iframe_html($viewData); + + return YoFLA360()->Frontend()->format_plugin_output($content); + } + + /** + * Returns a list of uploaded products, that can be assigned as a 360 view + * for this product. + * + * Output format is an array for the woocommerce_wp_select function + * + * + * @see yofla_360_tab_options + * @see YoFLA360Utils + */ + private function _get_products_list() + { + $result = array(); + $result[''] = 'No 360° view is assigned'; + + $list_raw = YoFLA360()->Utils()->get_yofla360_directories_list(false); + + foreach( $list_raw as $key => $value ) + { + $result[YOFLA_360_PRODUCTS_FOLDER.'/'.$value['name']] = $value['name']; + } + return $result; + } + + /** + * Returns false if product has no variations. + * Returns array of variation ids, if product has variations + * + * @return bool|mixed + */ + private function _is_variable_product() + { + //dev + return false; + $_pf = new WC_Product_Factory(); + $product = $_pf->get_product( get_the_ID() ); + + if( get_class($product) == 'WC_Product_Variable') + { + $variations = $product->get_available_variations(); + $result = array(); + foreach ($variations as $key => $value) { + $result[] = array("id" => $value['variation_id'], "name" => reset($value['attributes'])); + } + return $result; + } + return false; //no variable product + } + +}//class Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default/button-down.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/class-yofla360-activation.php =================================================================== diff -u --- plugins/360-product-rotation/includes/class-yofla360-activation.php (revision 0) +++ plugins/360-product-rotation/includes/class-yofla360-activation.php (revision 1309) @@ -0,0 +1,49 @@ +yofla_360_check_products_folder_initialized(); + } + + /** + * Creates yofla360 folder in uploads, if it already does not exist + * + * Checks if settings ini in uploads/yofla360 folder exists, creates if not + * + */ + private function yofla_360_check_products_folder_initialized() + { + $wp_uploads = wp_upload_dir(); + $products_path = $wp_uploads['basedir'].'/'.YOFLA_360_PRODUCTS_FOLDER.'/'; + $settings_path = $products_path.'settings.ini'; + $settings_source = YOFLA_360_PLUGIN_PATH.'/includes/yofla_3drt/settings.ini'; + + if(!file_exists($settings_path)){ + //create directory + wp_mkdir_p($products_path); + + //copy settings file + copy($settings_source,$settings_path); + } + } + + + + +}//class Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default/button-pan.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default/button-up.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-white/button-zoom-out.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-white/button-rotate.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/player_files/rotatetool.js =================================================================== diff -u --- plugins/360-product-rotation/includes/yofla_3drt/player_files/rotatetool.js (revision 0) +++ plugins/360-product-rotation/includes/yofla_3drt/player_files/rotatetool.js (revision 1309) @@ -0,0 +1,191 @@ +/** + * 3D Rotate Tool by YoFLA.com + * + * @author Matus Laco, www.yofla.com + * @description Script for rotating images to provide a 3D look of an object + * @sicne 06/2010 + * @updated 2016-06-22 + * @version 2.0.8 + * @homepage http://www.yofla.com/3d-rotate/ + * + * @license : You many not delete/modify this license or authorship info + * @license_type : Free License, You may not remove the YoFLA link in the top left corner. + * @licensed_to : Licensed to user who downloaded the 3DRT SetupUtility from http://www.yofla.com/3d-rotate/ + * @authorized_domains : Free version can be run on unlimited websites. + * + * 3rd Party licenses: + * Wave Pre-loader: MIT (https://github.com/tobiasahlin/SpinKit) + * + */ + +var RotateTool="object"===typeof RotateTool?RotateTool:{instances:{},logLevel:"none",isLogEnabled:!1,isLogToScreen:!1,isInitialized:!1,isTouchDevice:!1,isSupportedBrowser:null,isFullscreenBrowserAPISupported:!1,isCustomEventSupported:!1,isCanvasSupported:!1,isChrome:!1,isIE:!1,isFF:!1,authorizedDomains:"Free version can be run on unlimited websites.",scriptVersion:"2.0.8",licenseType:"Free License, You may not remove the YoFLA link in the top left corner.",eventsTouchStart:{forMouse:"mousedown",forTouch:"touchstart"},eventsTouchEnd:{forMouse:"mouseup",forTouch:"touchend"}, +eventsTouchMove:{forMouse:"mousemove",forTouch:"touchmove"},eventsMouseOver:{forMouse:"mouseover",forTouch:void 0},eventsMouseOut:{forMouse:"mouseout",forTouch:void 0},eventsMouseLeave:{forMouse:"mouseleave",forTouch:void 0},listeners:[],ON_IMAGE:"3drt_on_image",ROTATION_START:"3drt_rotation_start",ROTATION_STOP:"3drt_rotation_stop",ZOOM_START:"3drt_zoom_start",ZOOM_STOP:"3drt_zoom_stop",ZOOM_CHANGE:"3drt_zoom_change",ZOOM_RESET:"3drt_zoom_reset",PAN_START:"3drt_pan_start",PAN_STOP:"3drt_pan_stop", +TURN_START:"3drt_turning_start",TURN_STOP:"3drt_turning_stop",HOTSPOT_HOVER:"3drt_hotspot_hover",HOTSPOT_HOVER_OUT:"3drt_hotspot_hover_out",HOTSPOT_OPEN:"3drt_hotspot_open",TOOLTIP_SHOW:"3drt_tooltip_show",TOOLTIP_HOVER:"3drt_tooltip_hover",TOOLTIP_HOVER_OUT:"3drt_tooltip_hover_out",PRELOADER_LOADED:"3drt_preloader_image_loaded",PRELOADER_PROGRESS:"3drt_preloader_progress",IMAGES_LOADED:"3drt_images_loaded",TOGGLE_ROTATE_MODE:"3drt_toggle_rotate_mode",TOGGLE_PAN_MODE:"3drt_toggle_pan_mode",MOUSEWHEEL_ZOOM:"3drt_mousewheel_zoom", +CLICK_OBJECT:"3drt_click_object",BUTTON_PLAY_PAUSE_CLICK:"3drt_button_play_pause_click",BUTTON_ARROW_LEFT_CLICK:"3drt_button_arrow_left_click",BUTTON_ARROW_RIGHT_CLICK:"3drt_button_arrow_right_click",BUTTON_FULLSCREEN_CLICK:"3drt_button_fullscreen_click",BUTTON_ZOOMSLIDER_CLICK:"3drt_button_zoomslider_click",BUTTON_ROTATE_CLICK:"3drt_button_toggle_rotate_click",BUTTON_PAN_CLICK:"3drt_button_toggle_pan_click",BUTTON_ZOOM_OUT_CLICK:"3drt_button_toggle_zoom_out_click",BUTTON_ZOOM_IN_CLICK:"3drt_button_toggle_zoom_in_click", +BUTTON_START_CLICK:"3drt_start_click",CONTEXMENU_SHOW:"3drt_contex-menu_show",zIndex:{images:5,imageShown:6,imageHidden:4,canvas:7,preloaderImage:9,preloader:10,customLayer:15,clickPane:20,buttons:30,hotspots:40,preloaderLarge:45,toolTip:50,overlay:100,hotspotImage:110,hotspotVideo:110,hotspotTitle:120,customLogo:130,contexMenu:150,hotspotCloseButton:1100,info:900},initialize:function(){this.isInitialized||(this.defaultGAevents=[this.CLICK_OBJECT,this.BUTTON_PLAY_PAUSE_CLICK,this.BUTTON_ARROW_LEFT_CLICK, +this.BUTTON_ARROW_RIGHT_CLICK,this.BUTTON_FULLSCREEN_CLICK,this.BUTTON_ZOOMSLIDER_CLICK,this.BUTTON_ROTATE_CLICK,this.BUTTON_PAN_CLICK,this.HOTSPOT_HOVER,this.HOTSPOT_OPEN,this.CONTEXMENU_SHOW,this.ZOOM_START],this.addPolyFills(),this.detectBrowsers(),this.isSupportedBrowser=this.supportedBrowserCheck(),this.isTouchDevice="ontouchstart"in document.documentElement,this.isMobileDevice=this.mobileCheck(),this.isFullscreenBrowserAPISupported=this.fullscreenBrowserCheck(),this.isRunningInIframe=this.isInIframe(), +this.isCustomEventSupported=this.detectCustomEventSupport(),this.isCanvasSupported=this.detectCanvasSupport(),this.yofla_360_is_preview=!1,this.addDefaultStyleSheets(),this.addTabInactivityObserver(),window.addEventListener?window.addEventListener("load",this.onPageLoaded,!1):window.attachEvent("onload",this.onPageLoaded),this.addGlobalFunctions(),this.isInitialized=!0)},addDefaultStyleSheets:function(){var a;a="div.rotate-tool-instance{";a+="}";a+="img.rotate-tool-image";a+="{";a+=" max-width: none;"; +a+=" vartical-align: none;";a+=" -moz-box-sizing: none;";a+="}";a+="div.rotate-tool-spacer{";a+=" background-image: url(https://www.yofla.com/cdn/rotatetool/spacer.gif);";a+=" background-repeat: no-repeat";a+="}";a+="div.rotate-tool-hotspot-default{";a+="}";a+=".rotate-tool-unselectable{";a+=" -moz-user-select: none;";a+=" -webkit-user-select: none;";a+=" -ms-user-select: none;";a+=" user-select: none;";a+=" -webkit-user-drag: none;";a+=" user-drag: none;";a+="}";a+= +"\t\t.yp_wave {";a+="\t\t\tmargin: 100px auto;";a+="\t\t\twidth: 50px;";a+="\t\t\theight: 30px;";a+="\t\t\ttext-align: center;";a+="\t\t\tfont-size: 10px;";a+="\t\t}";a+="\t\t.yp_wave > div {";a+="\t\t\tbackground-color: #333;";a+="\t\t\theight: 100%;";a+="\t\t\twidth: 6px;";a+="\t\t\tdisplay: inline-block;";a+="\t\t\t-webkit-animation: stretchdelay 1.2s infinite ease-in-out;";a+="\t\t\tanimation: stretchdelay 1.2s infinite ease-in-out;";a+="\t\t}";a+="\t\t.yp_wave .rect2 {";a+="\t\t\t-webkit-animation-delay: -1.1s;"; +a+="\t\t\tanimation-delay: -1.1s;";a+="\t\t}";a+="\t\t.yp_wave .rect3 {";a+="\t\t\t-webkit-animation-delay: -1.0s;";a+="\t\t\tanimation-delay: -1.0s;";a+="\t\t}";a+="\t\t.yp_wave .rect4 {";a+="\t\t\t-webkit-animation-delay: -0.9s;";a+="\t\t\tanimation-delay: -0.9s;";a+="\t\t}";a+="\t\t.yp_wave .rect5 {";a+="\t\t\t-webkit-animation-delay: -0.8s;";a+="\t\t\tanimation-delay: -0.8s;";a+="\t\t}";a+="\t\t@-webkit-keyframes stretchdelay {";a+="\t\t\t0%, 40%, 100% { -webkit-transform: scaleY(0.4) }";a+="\t\t20% { -webkit-transform: scaleY(1.0) }"; +a+="\t\t}";a+="\t\t@keyframes stretchdelay {";a+="\t\t\t0%, 40%, 100% {";a+="\t\t\t\ttransform: scaleY(0.4);";a+="\t\t\t-webkit-transform: scaleY(0.4);";a+="\t\t} 20% {";a+="\t\t\ttransform: scaleY(1.0);";a+="\t\t\t-webkit-transform: scaleY(1.0);";a+="\t\t\t}";a+="\t\t}";a+="span.rotate-tool-tooltip{";a+=" font-family: sans-serif;";a+=" font-size: 13px;";a+=" color: #0c0c0c;";a+=" padding: 5px;";a+=" opacity: 0.85;";a+=" filter: Alpha(Opacity=85);";a+=" border-radius: 5px;";a+= +" border: 1px solid gray;";a+=" background-color: rgba(230,230,230,0.5);";a+="}";a+="ul.rotate-tool-contex-menu {";a+="border:0px solid #666;";a+="min-width:150px;";a+="max-width:350px;";a+="list-style:none;";a+="padding:0;";a+="margin:0;";a+="cursor:pointer;";a+="}";a+="ul.rotate-tool-contex-menu li {";a+="text-align:left;";a+="padding:3px 10px 3px 5px;";a+="margin:0;";a+="cursor:pointer;";a+='font-family: "Lucida Grande", Verdana, Arial, "Bitstream Vera Sans", sans-serif;';a+="text-decoration:none;"; +a+="color:#fff;";a+="font-size:12px;";a+="border-top:1px solid #fff;";a+="border-left:1px solid #fff;";a+="border-bottom:1px solid #999;";a+="border-right:1px solid #999;";a+="background-color: rgba(0,0,0,.7);";a+="}";a+="ul.rotate-tool-contex-menu li.selected,";a+="ul.rotate-tool-contex-menu li:hover {";a+="color:#fff;";a+="background-color: rgba(56,117,215,.7);";a+="cursor:pointer;";a+="}";a+="ul.rotate-tool-contex-menu li.selected:hover {";a+="color:#333;";a+="background-color:#eee;";a+="cursor:pointer;"; +a+="}";a+="ul.rotate-tool-contex-menu li.disabled,";a+="ul.rotate-tool-contex-menu li:hover.disabled {";a+="background-color: #eee;";a+="color:#999;";a+="cursor:pointer;";a+="}";var d=document.getElementsByTagName("head")[0],b=document.createElement("style");b.type="text/css";b.styleSheet?b.styleSheet.cssText=a:b.appendChild(document.createTextNode(a));d.appendChild(b)},onPageLoaded:function(){RotateTool.execRotateTool()},execRotateTool:function(){if(!1!=RotateTool.isObjectEmpty(RotateTool.instances)&& +document.querySelectorAll)for(var a=document.querySelectorAll("[data-rotate-tool]"),d,b,h,c,g=0;g"+this.logTarget.innerHTML:this.createLogTarget()},createLogTarget:function(){var a=this.get();a&&a.target&&(this.logTarget=document.createElement("div"),this.logTarget.className="rotate-tool-log-target",this.logTarget.style.position="absolute",this.logTarget.style.width="50%",this.logTarget.style.height="50%",this.logTarget.style.border="1px solid red;",this.logTarget.style.zIndex=999,this.logTarget.style.backgroundColor="yellow",this.logTarget.style.opacity= +"0.65",this.logTarget.style.filter="alpha(Opacity=65)",this.logTarget.style.overflow="scroll",this.logTarget.style.color="black",this.logTarget.style.fontSize="11px",this.logTarget.style.textAlign="left",this.logTarget.innerHTML="test",a.target.appendChild(this.logTarget))},validateRotationData:function(a){return a.target?!0:"Target not defined!"},rotateToolFactory:function(a){var d={THRESHOLD_TIME_ROTATE_PAN_DIF:500,THRESHOLD_TIME_DRAG_STOP_ROTATE:100,elementId:a.target,target:document.getElementById(a.target), +path:a.path,configFile:a.configFile?a.configFile:"config.js",isConfigJsLoaded:!1,productId:a.id,themeUrl:a.themeUrl,forcedSize:a.size,settings:{},originalTargetWidth:null,originalTargetHeight:null,originalTargetRatio:null,targetWidth:400,targetHeight:300,targetRation:null,zIndex:{},buttons:{},vSteps:null,hSteps:null,imageNodes:null,rotateIntervalDuration_original:null,rotateIntervalDuration_current:null,rotateIntervalThresholdStop:null,rotateInterval:null,rotateImages:[],imagesGrid:[],lastImgH:null, +lastImgV:null,newImgH:null,newImgV:null,dx:0,dy:0,previousDx:0,isRotating:!1,wasRotating:!1,currentImage:null,previousImage:null,rotateOnceCounter:0,timeLastImageShown:0,durationMultiplier:1,isZoomed:!1,scaleInPercent:100,relativeX:0.5,relativeY:0.5,isRotationWhenZoomed:!1,zoomStepInPercent:100,zoomStepInRatio:100,zoomSteps:4,maxZoomThreshold:105,objectWidth:400,objectHeight:400,dragInterval:null,touchStart:null,touchMove:null,touchMovePrevious:null,touchMovePreviousDrag:null,isGestureStart:!1,gestureStartValue:null, +lastYatDyChange:null,touchEnd:null,mouseWheelZooms:!1,currentZoomValue:0,zoomInPercent:100,isMouseMoved:!1,imageLoadErrorsString:"",imageLoadErrorCount:0,imagesToLoad:null,imagesLoaded:0,largeImagesLoaded:0,imagesProcessed:0,isImagesLoaded:!1,preloader:null,usingSmallImages:null,startButtonToggled:!1,loadImagesInSequence:!1,hotspotInstances:null,activeHotspot:null,hotspotOverlay:null,hotspotImage:null,isHotspotsVisible:!0,tooltips:{},daggedObject:null,clickPaneTouchMoveReference:null,clickPaneTouchEndReference:null, +requestRescaleFlag:!1,mouseWheelCounter:0,clickPane:null,sliderHandle:null,handleMaxX:null,infoPanel:null,clickPane:null,sliderHandle:null,silderContainer:null,isGAenabled:a.gaData.isEnabled&&"true"==a.gaData.isEnabled.toLowerCase(),gaEnabledEvents:a.gaData.enabledEvents instanceof Array?a.gaData.enabledEvents:RotateTool.defaultGAevents,gaTrackingId:a.gaData.trackingId,gaCategory:a.gaData.category,gaLabel:a.gaData.label,setLevel:function(b){!1!==this.isImagesLoaded&&(this.lastImgV=b,b=this.dx,this.dx= +this.dy=0,this.showNextImage(),this.dx=b)},setImage:function(b,a){a="undefined"!==typeof a?a:!0;if(!isNaN(b)&&!1!==this.isImagesLoaded){var c=this.dx;this.stopRotation();b>this.imagesToLoad-1||(1==this.vSteps||bRotateTool.authorizedDomains.indexOf(c)&&"localhost"!==c&&b.unshift({title:"Domain not authorized!",action:"URL_OPEN",data:"http://www.yofla.com/3d-rotate/support/domain-not-authorized/"})}0'+b.title.text.toString()+""};this.positionToolTip=function(){var c=10,k=10,l,n=this.htmlElement.offsetWidth,m=this.htmlElement.offsetHeight;if(!(n>=d.targetWidth||m>=d.targetHeight)){a instanceof Array&&2==a.length?(c=a[0],k=a[1],l=20):("IMG"==a.tagName&&(a=a.parentNode),k=a.offsetTop,c=a.offsetLeft,l=a.offsetWidth); +switch(this.positioning){case "CENTER_TO_HOTSPOT":c=Math.floor(c-0.5*n);k=Math.floor(k-0.5*m);break;default:c=c+l+5,"mousewheel_tooltip"==b.id&&(k+=10)}c=Math.max(c,10);k=Math.max(k,10);c=Math.min(c,d.targetWidth-n-10);k=Math.min(k,d.targetHeight-m-10)}this.htmlElement.style.left=c+"px";this.htmlElement.style.top=k+"px"};this.createToolTipWrapper=function(){var b=document.createElement("div");d.target.appendChild(b);b.className="rotate-tool-tooltip-wrapper rotate-tool-unselectable";b.style.position= +"absolute";b.style.zIndex=RotateTool.zIndex.toolTip;b.style.display="none";b.style.opacity=0;b.style.cursor="pointer";RotateTool.addClickOrTouchListener(b,RotateTool.eventsTouchEnd,RotateTool.bind(this,this.mouseOrTouchEnd));RotateTool.addClickOrTouchListener(b,RotateTool.eventsMouseLeave,RotateTool.bind(this,this.mouseLeave));RotateTool.addClickOrTouchListener(b,RotateTool.eventsMouseOver,RotateTool.bind(this,this.mouseOver));return b};this.mouseOrTouchEnd=function(){this.hide();RotateTool.isTouchDevice|| +(this.activeHotspot=null,d.executeHotspot(b))};this.mouseLeave=function(){this.hide();c.dispatchEvent(RotateTool.TOOLTIP_HOVER_OUT,{tooltipObject:this})};this.mouseOver=function(){clearTimeout(d.hideToolTipsInterval);c.dispatchEvent(RotateTool.TOOLTIP_HOVER,{tooltipObject:this})};this.show=function(a){this.htmlElement&&(d.hideToolTips(b.id),this.timeoutToolTipOnHideCompleted?(this.htmlElement.style.opacity=1,this.htmlElement.style.display="block",clearTimeout(this.timeoutToolTipOnHideCompleted),this.timeoutToolTipOnHideCompleted= +null):(this.htmlElement.style.display="block",this.positionToolTip(),this.htmlElement.style.MozTransition="opacity 0.4s ease-in-out",this.htmlElement.style.WebkitTransition="opacity 0.4s ease-in-out",this.htmlElement.style.opacity=1,this.timeoutToolTipOnHideCompleted=null,c.dispatchEvent(RotateTool.TOOLTIP_SHOW,{hotspotData:b,tooltipElement:this.htmlElement}),a&&(a=parseInt(a),this.autoHideTimout=setTimeout(RotateTool.bind(this,this.hide),a))))};this.hide=function(){this.htmlElement.style.MozTransition= +"opacity 0.3s ease-in-out";this.htmlElement.style.WebkitTransition="opacity 0.3s ease-in-out";this.htmlElement.style.opacity=0;this.timeoutToolTipOnHideCompleted=setTimeout(RotateTool.bind(this,this.onHideCompleted),300)};this.onHideCompleted=function(){this.htmlElement.style.display="none";this.timeoutToolTipOnHideCompleted=null};this.initialize=function(){!0==b.keepVisible&&(this.keepVisible=!0);b.toolTip&&typeof("object"==b.toolTip.html)?this.createHtmlToolTip():b.title&&this.createSimpleToolTip()}; +this.initialize()}(b,this)},hotspotTouchStartListener:function(b,a){},hotspotTouchEndListener:function(b,a){},executeHotspot:function(b,a){},executeJavascriptHotspot:function(b,a){},executeUrlHotspot:function(b){},executeImageHotspot:function(b){},executeVideoHotspot:function(b){},showHotspotCloseButton:function(){this.closeHotspotButton||this.createHotspotCloseButton();this.closeHotspotButton.style.display="block"},hideHotspotCloseButton:function(){this.closeHotspotButton&&(this.closeHotspotButton.style.display= +"none")},createHotspotCloseButton:function(){var b=document.createElement("div");b.className="rotate-tool-close-hotspot-button-wrapper";var a=document.createElement("div");a.className="rotate-tool-close-hotspot-button-content";a.innerHTML="✖";b.appendChild(a);b.style.zIndex=RotateTool.zIndex.hotspotCloseButton;b.style.display="none";this.target.appendChild(b);RotateTool.addClickOrTouchListener(b,{forTouch:"touchend",forMouse:"click"},RotateTool.bind(this,this.onButtonHotspotCloseListener)); +this.closeHotspotButton=b},onButtonHotspotCloseListener:function(){this.hideHotspotCloseButton();this.hideVideoHotspot()},createImageHotspotOvelay:function(){},loadHotspotImage:function(b){},hotspotImageError:function(b){},hotspotImageLoaded:function(b){},fitHotspotImageToTarget:function(b){},addHotspotTitle:function(b){},hideImageHotspot:function(){},hideVideoHotspot:function(){},redrawHotspots:function(){},hideAllHotspots:function(){},showFixedHotspots:function(){},showHotspot:function(b){},getWorkingDx:function(){return 0!= +this.previousDx?this.previousDx:0>this.settings.rotation.rotateDirection?-1:1},startRotation:function(){this.dispatchEvent(RotateTool.ROTATION_START);0==this.dx&&(this.dx=this.getWorkingDx());this.isRotating=!0;this.rotateIntervalDuration_current=this.rotateIntervalDuration_original},stopRotation:function(){0!=this.dx&&(this.dispatchEvent(RotateTool.ROTATION_STOP),this.isRotating=!1,this.previousDx=this.dx,this.dx=0,this.setScale(this.scaleInPercent,this.currentImage))},stopRotationSmooth:function(){this.dispatchEvent(RotateTool.ROTATION_STOP); +this.durationMultiplier=1.05},startRotationSmooth:function(){this.dispatchEvent(RotateTool.ROTATION_START);this.dx=this.getWorkingDx();this.rotateIntervalDuration_current=1.5*this.rotateIntervalDuration_original;this.durationMultiplier=0.95},rotateLoop:function(){1!=this.durationMultiplier&&(this.rotateIntervalDuration_current*=this.durationMultiplier,this.rotateIntervalDuration_current>this.rotateIntervalDuration_original&&(this.rotateIntervalDuration_current=this.rotateIntervalDuration_original, +this.durationMultiplier=1));window.requestAnimationFrame&&window.requestAnimationFrame(RotateTool.bind(this,this.rotateLoop));this.okNextImage()&&this.showNextImage()},showNextImage:function(b){null==this.lastImgH?this.newImgV=this.newImgH=0:(this.newImgH=this.lastImgH+this.dx,this.newImgV=this.lastImgV+this.dy);this.newImgH>=this.hSteps&&(this.newImgH=0,this.bounce&&(this.newImgH=this.hSteps-1,this.dx=-this.dx));0>this.newImgH&&(this.newImgH=this.hSteps-1,this.bounce&&(this.newImgH=0,this.dx=-this.dx)); +this.newImgV>=this.vSteps&&(this.newImgV=this.vSteps-1);0>this.newImgV&&(this.newImgV=0);try{if(this.currentImage=this.rotateImages[this.imagesGrid[this.newImgV][this.newImgH]],!this.currentImage)return}catch(a){return}if(!this.isDragging||!this.currentImage||this.currentImage!=this.previousImage){this.requestRescaleFlag&&this.setRescaleFlag();var c={imgH:this.newImgH,imgV:this.newImgV,imgSrc:this.currentImage.src,imageId:this.currentImage.idNumber,fileName:this.currentImage.fileName};!1===("undefined"=== +typeof b?!1:b)&&this.dispatchEvent(RotateTool.ON_IMAGE,c);this.setScale(this.scaleInPercent,this.currentImage);this.useCanvas?this.redrawCanvas():(this.previousImage&&(RotateTool.isIE||RotateTool.isFF?this.previousImage.style.visibility="hidden":this.previousImage.style.opacity=0),this.currentImage&&(RotateTool.isIE||RotateTool.isFF?this.currentImage.style.visibility="visible":this.currentImage.style.opacity=1));this.hideToolTips();this.previousImage=this.currentImage;this.redrawHotspots();this.lastImgH= +this.newImgH;this.lastImgV=this.newImgV;this.settings.rotation.rotateOnce&&(this.rotateOnceCounter++,this.rotateOnceCounter==this.hSteps+1&&this.stopRotation());this.timeLastImageShown=Date.now()}},redrawCanvas:function(){this.useCanvas&&this.currentImage&&this.currentImage.complete&&(this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.drawImage(this.currentImage,parseInt(this.currentImage.style.left)||0,parseInt(this.currentImage.style.top)||0,parseInt(this.currentImage.style.width), +parseInt(this.currentImage.style.height)))},okNextImage:function(){var b=Date.now()-this.timeLastImageShown;if(!this.previousImage)return!0;if(0==this.dx&&0==this.dy)return!1;if(b>this.rotateIntervalDuration_current)return!0},setScale:function(b,a){if(a&&!a.scaled&&b){b=Math.max(100,b);this.scaleInPercent=b=Math.min(b,this.settings.control.maxZoom);var c=1,c=this.targetRatiothis.targetWidth&&(c=Math.min(c, +0),c=Math.max(c,k));f>this.targetHeight&&(d=Math.min(d,0),d=Math.max(d,l));a.style.top=""+d+"px";a.style.left=""+c+"px"}else this.relativeY=this.relativeX=0.5,cthis.THRESHOLD_TIME_DRAG_STOP_ROTATE||!1==this.settings.control.enableSwing)this.dy=this.dx=0,this.rotateIntervalDuration_current=this.rotateIntervalDuration_original},dragListener:function(a){var d=a=0,c=0.5;this.bounce&&(c=1);if(!this.isGestureStart&&this.touchStart&&this.touchMove){a=this.touchStart.y-this.touchMove.y; +var d=this.touchStart.x-this.touchMove.x,g=RotateTool.signum(this.touchMovePreviousDrag.x-this.touchMove.x);this.previousDx=g;0==this.previousDx&&(this.previousDx=RotateTool.signum(d));this.settings.control.reverseDrag&&(d*=-1,this.previousDx*=-1);c=Math.floor(this.hSteps*(d/this.objectWidth)*c);c=0<=c?c%this.hSteps:-(Math.abs(c)%this.hSteps);c=this.touchStart.lastImgH+c;0>c?(c+=this.hSteps,this.bounce&&(c=0)):c>=this.hSteps&&(c%=this.hSteps,this.bounce&&(c=this.hSteps-1));var e=this.lastImgV;if(1< +this.vSteps&&!1==this.settings.rotation.multilevel.disableDrag){var f=Math.floor(this.vSteps*(a/(0.5*this.objectHeight))),k=1;this.settings.rotation.multilevel.reverseDrag&&(k=-1);0==g&&(e=this.touchStart.lastImgV+f*k,isNaN(e)&&(e=0),0>e&&(e=0),e>=this.vSteps&&(e=this.vSteps-1))}if(1==this.vSteps&&25>Math.abs(d)&&100==this.scaleInPercent&&!0==this.settings.control.touchPageScroll&&!0==RotateTool.isTouchDevice){d=window;RotateTool.isRunningInIframe&&(d=window.parent);try{d.scrollTo(this.windowXPositionAtTouchStart, +this.windowYPositionAtTouchStart+a)}catch(l){}}this.dy=this.dx=0;this.lastImgH=c;this.lastImgV=e;a=this.timeLastImageShown;this.showNextImage();this.rotateIntervalDuration_current=Date.now()-a;this.touchMovePreviousDrag=this.touchMove}},evalClickPaneTouchEnd:function(a,d){var c=!1,g=1;if(d&&a){var e=Math.abs(d.x-a.x),f=Math.abs(d.y-a.y);RotateTool.isTouchDevice&&(g=8);e+f<=g&&(c=!0);return c}},evalClickPaneTouchMove:function(a,d){var c=d.x-a.x,g=d.y-a.y,e=!1;if(!(RotateTool.isTouchDevice&&10>=Math.abs(c)+ +Math.abs(g))){Math.abs(g)>Math.abs(c)&&1==this.vSteps&&(e=!0);var f=d.timeStamp-a.timeStamp;!1==this.isMouseMoved&&(f>this.THRESHOLD_TIME_ROTATE_PAN_DIF||e&&this.settings.control.switchToPanOnYmovement)&&this.isRotationWhenZoomed&&(this.wasRotationWhenZoomed=!0,this.toggleRotationInZoomMode(!1));if(!this.isRotationWhenZoomed){var c=this.currentImage.startOffsetX+c,g=this.currentImage.startOffsetY+g,e=parseInt(this.currentImage.style.width),f=parseInt(this.currentImage.style.height),k=this.targetWidth- +e-0,l=this.targetHeight-f-0;e>this.targetWidth&&(c=Math.min(c,0),c=Math.max(c,k));f>this.targetHeight&&(g=Math.min(g,0),g=Math.max(g,l));c>0.5*this.targetWidth&&(c=Math.floor(0.5*this.targetWidth));g>0.5*this.targetHeight&&(g=Math.floor(0.5*this.targetHeight));c+e<0.5*this.targetWidth&&(c=Math.floor(0.5*this.targetWidth)-e);g+f<0.5*this.targetHeight&&(g=Math.floor(0.5*this.targetHeight)-f);if(this.currentImage)try{this.currentImage.style.left=c+"px",this.currentImage.style.top=g+"px",this.relativeX= +-(c-0.5*this.targetWidth)/parseInt(e),this.relativeY=-(g-0.5*this.targetHeight)/parseInt(f),this.useCanvas&&(this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.drawImage(this.currentImage,parseInt(this.currentImage.style.left),parseInt(this.currentImage.style.top),parseInt(this.currentImage.style.width),parseInt(this.currentImage.style.height)))}catch(n){}this.setRescaleFlag()}this.redrawHotspots();this.isMouseMoved=!0}},evalClickOnPane:function(a){this.dispatchEvent(RotateTool.CLICK_OBJECT); +this.settings.control.clickUrl?(this.stopRotation(),this.openClickUrl()):(this.dx=this.wasRotating?this.getWorkingDx():0,this.checkDoubleClick(a)&&100!=this.scaleInPercent&&this.resetZoom(),!1==this.settings.control.rotateOnClick&&(this.dx=this.wasRotating?0:this.getWorkingDx()),this.toggleRotation())},checkDoubleClick:function(a){var d=a.timeStamp;if(this.previousTouchEndTime&&(a=d-this.previousTouchEndTime,300>a))return!0;this.previousTouchEndTime=d;return!1},openClickUrl:function(){var a=this.settings.control.clickUrl, +d=this.settings.control.clickUrlTarget;a&&(d||(d="_top"),window.open(a,d))},MouseWheelHandler:function(a){a=window.event||a;var d=Math.max(-1,Math.min(1,a.wheelDelta||-a.detail));try{a.preventDefault()}catch(c){}this.settings.control.mouseWheelZooms||a.ctrlKey||a.metaKey?(a=this.settings.control.maxZoom,this.settings.control.zoomInStopsRotation&&this.stopRotation(),d=1/(a-100)*(this.scaleInPercent-10*d)-100*(1/(a-100)),isNaN(d)||this.zoomRatioChangeListener(d,"mouseWheel")):(!0!==this.toolTipOnMouseWheelShown&& +!1==RotateTool.isTouchDevice&&(a=RotateTool.getLocalMousePosition(a),this.showToolTip({id:"mousewheel_tooltip",keepVisible:!0,title:{text:"Tip: use MouseWheel+Ctrl for Zooming"}},[a.x,a.y],3E3),this.toolTipOnMouseWheelShown=!0),this.stopRotation(),this.dx=d,this.mouseWheelCounter++,0==this.mouseWheelCounter%4&&this.showNextImage())},toggleRotation:function(){0==this.dx?this.startRotation():this.stopRotation()},windowResizeListener:function(a){this.wasRotating=0!=this.dx;this.stopRotation();this.resetZoom(); +this.updateTargetStyles();this.updateMaxZoom();this.imagesLoaded&&(this.currentImage&&(this.currentImage.scaled=!1),this.updateScalesOfImages(),this.redrawCanvas(),this.wasRotating?(this.dx=this.getWorkingDx(),this.startRotation()):(this.setScale(100,this.currentImage),this.showNextImage()),this.repositionFixedHotspots())},repositionFixedHotspots:function(){if(this.hotspotInstances)for(var a in this.hotspotInstances)this.hotspotInstances[a].isFixed&&this.positionFixedHotspot(this.hotspotInstances[a])}, +updateScalesOfImages:function(){if(this.imagesLoaded)for(var a,d=0;da.normalImageWidth)a.srcCurrent!=a.srcLarge&&(a.largeImageWidth?(a.originalWidth=a.largeImageWidth,a.originalHeight=a.largeImageHeight,a.scaled=!1,RotateTool.setImageSource(a,a.srcLarge),!1==this.isSpinning()&&a.srcLarge==this.currentImage.srcLarge&&this.showNextImage()): +this.loadLargeImage(a,!0));else if(a.srcCurrent!=a.srcNormal&&(RotateTool.setImageSource(a,a.srcNormal),a.originalWidth=a.normalImageWidth,a.originalHeight=a.normalImageHeight,wasScaled=a.scaled=!1,!1==this.isSpinning()&&a.srcNormal==this.currentImage.srcNormal)){var d=this;setTimeout(function(){d.currentImage.onload=function(){d.showNextImage()};d.currentImage.src=d.currentImage.srcNormal},300)}},fullscrentChangedListener:function(){RotateTool.isFullscreen();this.windowResizeListener()},zoomRatioChangeListener:function(a, +d){if("undefined"!=typeof a&&!isNaN(a)){this.settings.control.zoomInStopsRotation&&this.stopRotation();a=Math.max(0,a);this.currentZoomValue=a=Math.min(1,a);var c=100+(this.settings.control.maxZoom-100)*a;this.dispatchEvent(RotateTool.ZOOM_CHANGE,this.lastImgH);0==a&&this.dispatchEvent(RotateTool.ZOOM_RESET,this.lastImgH);this.currentImage&&(this.currentImage.scaled=!1,this.setScale(c,this.currentImage),this.currentImage.scaled=!1,this.currentImage&&(this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height), +this.ctx.drawImage(this.currentImage,parseInt(this.currentImage.style.left),parseInt(this.currentImage.style.top),parseInt(this.currentImage.style.width),parseInt(this.currentImage.style.height))));this.requestRescaleFlag=!0;this.isZoomed=0!==a;"sliderHandle"!=d&&this.updateSliderHandlePosition(a);"zoom"==this.settings.userInterface.showToggleRotateButton&&this.toggleToggleRotatePanButtons(0!=a)}},toggleToggleRotatePanButtons:function(a){this.buttons["rotate-tool-button-toggle-pan"]&&this.buttons["rotate-tool-button-toggle-rotate"]&& +(a?(this.buttons["rotate-tool-button-toggle-pan"].style.display="block",this.buttons["rotate-tool-button-toggle-rotate"].style.display="block"):(this.buttons["rotate-tool-button-toggle-pan"].style.display="none",this.buttons["rotate-tool-button-toggle-rotate"].style.display="none"))},resetZoom:function(){this.zoomRatioChangeListener(0,null);this.requestRescaleFlag=!0},reportBrowserNotSupported:function(){this.target.innerHTML='Your browser does not support this 360° object rotation script.'}, +addPlayerInfo:function(){var a=document.createElement("div"),d=document.createElement("a");d.href="http://www.yofla.com/3d-rotate/player-about/";d.target="_blank";d.innerHTML="360 by YoFLA";d.style.textDecoration="none";d.style.color="navy";d.style.fontFamily="sans-serif";d.style.fontSize="10px";d.style.paddingLeft="5px";d.style.paddingRight="5px";a.appendChild(d);this.target.appendChild(a);a.style.position="absolute";a.style.top="2px";a.style.right="4px";a.style.zIndex=this.zIndex.info+1;a.style.background= +"white";a.style.borderRadius="5px";a.style.opacity=0.75;a.style.filter="alpha(Opacity=75)"},addEventListener:function(a,d,c){c="undefined"===typeof c?!1:c;!1===RotateTool.isCustomEventSupported?document.addEventListener(a,d,c):this.target.addEventListener(a,d,c)},removeEventListener:function(a,d,c){c="undefined"===typeof c?!1:c;!1===RotateTool.isCustomEventSupported?document.removeEventListener(a,d,c):this.target.removeEventListener(a,d,c)},dispatchEvent:function(a,d){this.detectZoomStart(a);this.isGAenabled&& +this.reportGAevent(a,d);if(!1===RotateTool.isCustomEventSupported)try{var c=document.createEvent("Event");c.initEvent(a,!0,!0);c.detail=d;document.dispatchEvent(c)}catch(g){}else c=new CustomEvent(a,{detail:d}),c.bubbles=!0,this.target.dispatchEvent(c)},detectZoomStart:function(a){a==RotateTool.ZOOM_CHANGE&&this.lastEventName!=RotateTool.ZOOM_CHANGE&&this.dispatchEvent(RotateTool.ZOOM_START);this.lastEventName=a},reportGAevent:function(a,d){},rotateToolInstanceLast:"no colon"};d.initialize();return d}, +fixPathVariable:function(a){if(a&&0a&&(b="  ");this.percentDiv&&(this.percentDiv.innerHTML=b+a+"%");this.progressBar&&(this.progressBar.style.width=a+"%")},destroy:function(){this.percentDiv.innerHTML="";this.percentDiv.style.display="none";this.containerDiv.removeChild(this.percentDiv);this.containerDiv.removeChild(this.backgroundDiv);this.containerDiv.removeChild(this.progressBar);this.targetDiv.removeChild(this.containerDiv);this.containerDiv=this.targetDiv=this.percentDiv=this.progressBar=this.backgroundDiv= +null}};e.initialize(a);return e},wavePreloader:function(a,d){var b=this,h=Number(d.background.alpha),c=Math.floor(100*h),g={percentDiv:null,targetDiv:null,containerDiv:null,initialize:function(a,f){this.containerDiv=document.createElement("div");this.containerDiv.className="rotate-tool-preloader";this.containerDiv.style.webkitTransform="translateZ(0)";this.containerDiv.style.MozTransform="translateZ(0)";this.containerDiv.style.msTransform="translateZ(0)";this.containerDiv.style.OTransform="translateZ(0)"; +this.containerDiv.style.transform="translateZ(0)";this.containerDiv.style.width="100%";this.containerDiv.style.height="100%";this.containerDiv.style.position="absolute";this.containerDiv.style.zIndex=b.zIndex.preloader;this.targetDiv=document.getElementById(a);this.targetDiv.appendChild(this.containerDiv);this.backgroundDiv=document.createElement("div");this.backgroundDiv.style.left="0px";this.backgroundDiv.style.zIndex=b.zIndex.preloader;this.backgroundDiv.style.position="absolute";this.backgroundDiv.style.top= +Math.round(this.targetDiv.offsetHeight/2)-33+"px";this.backgroundDiv.style.width="100%";this.backgroundDiv.style.height="50px";d.background.enabled&&(this.backgroundDiv.style.backgroundColor=d.background.color,this.backgroundDiv.style.opacity=h,this.backgroundDiv.style.filter="alpha(Opacity="+c+")");this.containerDiv.appendChild(this.backgroundDiv);this.percentDiv=document.createElement("div");this.percentDiv.style.zIndex=b.zIndex.preloader+1;this.percentDiv.style.position="relative";var k;k=""+('
');k=k+('\t
')+('\t
');k+='\t
';k+='\t
';k+='\t
';k+="
";this.percentDiv.innerHTML=k;this.percentDiv.setAttribute("id","preloader_"+this.targetDiv.id);this.containerDiv.appendChild(this.percentDiv)}, +update:function(a){},destroy:function(){this.percentDiv.innerHTML="";this.percentDiv.style.display="none";this.containerDiv.removeChild(this.percentDiv);this.containerDiv.removeChild(this.backgroundDiv);this.targetDiv.removeChild(this.containerDiv);this.containerDiv=this.targetDiv=this.percentDiv=this.progressBar=this.backgroundDiv=null}};g.initialize(a,d.color1);return g},addClickOrTouchListener:function(a,d,b){this.isTouchDevice?void 0!==d.forTouch&&this.addEventSimple(a,d.forTouch,b):void 0!== +d.forMouse&&this.addEventSimple(a,d.forMouse,b)},removeClickOrTouchListener:function(a,d,b){this.isTouchDevice?this.removeEventSimple(a,d.forTouch,b):this.removeEventSimple(a,d.forMouse,b)},bind:function(a,d,b){return function(){d.apply(a,arguments)}},signum:function(a){return 0a?-1:0},detectBrowsers:function(){var a=navigator.userAgent,d;this.isChrome=-1>>0,h=Number(d)||0,h=0>h?Math.ceil(h):Math.floor(h);for(0>h&&(h+=b);his_url_path( $src ) ) return false; + + $product_path_full = $this->get_full_product_path($src); + + $rotatetool_js_file_full_path = $product_path_full.'rotatetool.js'; + $config_file_full_path = $product_path_full.'config.js'; + + + if(file_exists($rotatetool_js_file_full_path) && file_exists($config_file_full_path) ) + { + return true; + } + else + { + return false; + } + } + + + /** + * Returns the full system url to the folder with 360 content + * + * The folder resides within wp uploads folder + * + * @param $src String Is the path relative to the wp uploads folder + * @return string + */ + public function get_full_product_path($src) + { + $src = trim($src,'/'); + $wp_uploads = wp_upload_dir(); + $product_path_full = trailingslashit ( trailingslashit($wp_uploads['basedir']) . $src ); + return $product_path_full; + } + + /** + * Returns the path to the product, relative to the uploads/yofla360 folder + * + * @param $src String + * @return string + */ + public function get_relative_product_path($src) + { + $src = trim($src,'/'); //remove starting '/' + $src = str_replace(YOFLA_360_PRODUCTS_FOLDER.'/','',$src); //remove starting yofla360/ + return $src; + } + + + /** + * Returns URL to the yofla360 folder within WP uploads + * + * @return string + */ + public function get_products_url() + { + $wp_uploads = wp_upload_dir(); + $uploads_url = trailingslashit ( $wp_uploads["baseurl"] ); + $products_url = $uploads_url.YOFLA_360_PRODUCTS_FOLDER.'/'; + return $products_url; + } + + /** + * Returns URL to the 360 view (value of path attribute) + * + * @param $viewData YoFLA360Viewdata + * @return string + */ + public function get_product_url($viewData) + { + if ( $this->is_url_path($viewData->src) ) + { + return $viewData->src; + } + else + { + $src = trim( $viewData->src,'/' ); + return $this->get_uploads_url().$src.'/'; + } + } + + /** + * Returns URL to the WP uploads folder + * + * @return string + */ + public function get_uploads_url() + { + $wp_uploads = wp_upload_dir(); + $uploads_url = trailingslashit ( $wp_uploads["baseurl"] ); + return $uploads_url; + } + + /** + * Returns system path to the yofla360 folder within WP uploads + * + * @return string + */ + public function get_products_path() + { + $wp_uploads = wp_upload_dir(); + return trailingslashit ( $wp_uploads['basedir'] ) . YOFLA_360_PRODUCTS_FOLDER.'/'; + } + + + /** + * Returns true if provided string is an url + * + * @param $str + * @return bool + */ + public function is_url_path($str) + { + if( (strpos($str,'http') === 0) || (strpos($str,'//') === 0 )) + { + return true; + } + else + { + return false; + } + } + + + /** + * Returns the url of an iframe for provided view + * + * @see construct_iframe_content + * @param $viewData YoFLA360ViewData + * @return string + */ + public function get_iframe_url($viewData) + { + + //check required src + if(empty($viewData->src)) + { + YoFLA360()->add_error("Src attribute not provided!"); + return false; + } + + //config file is present or absolute path + if($this->is_created_with_desktop_application($viewData->src) || $this->is_url_path($viewData->src)) + { + //pass short code parameters to iframe content creator url + $data = array( + 'src' => $viewData->src, + 'product_name' => $viewData->name, + 'ga_label' => $viewData->get_ga_label(), + 'ga_category' => $viewData->ga_category, + 'ga_tracking_id' => ($viewData->ga_enabled && $viewData->ga_tracking_id ) ? $viewData->ga_tracking_id : false + ); + + $iframe_url = YOFLA_360_PLUGIN_URL.'includes/iframe.php?'.http_build_query($data); + return $iframe_url; + } + //just images are uploaded + else + { + $iframe_url = $this->get_iframe_url_with_generated_config_file($viewData); + } + + return $iframe_url; + } + + + /** + * Returns url for the config.js file for product. + * + * If the config.js does, not exist, create it. + * + * @param $viewData YoFLA360Viewdata + * @return string + */ + public function get_config_url($viewData) + { + + //check if absolute url is set + if($this->is_url_path($viewData->src)){ + return trailingslashit($viewData->src).'config.js'; + } + + + $config_path = $this->get_full_product_path($viewData->src).'config.js'; + + if ( file_exists($config_path) ) + { + return $viewData->get_product_url().'config.js'; + } + //generate config file + else + { + $this->_init_rotate_tool_library(); + + //if cache attribute is set to true, use the cache + if( $viewData->is_cache_enabled){ + Rotate_Tool::$is_cache_disabled = false; + } + + $config_url = Rotate_Tool::get_config_url_for_product($this->get_relative_product_path($viewData->src)); + + return $config_url; + } + + } + + /** + * Currently, this function is only called when embedding using div & using just images. + * The theme falls backs to pure-white. + * + * Later, it will be possible to specify theme in shortcode + * Later, there will be a themes section in plugin. + * + */ + public function get_theme_url() + { + $url = YOFLA_360_PLUGIN_URL.'includes/yofla_3drt/themes/pure-white'; + return $url; + } + + + /** + * Returns url to a generated iframe.html page, that has contains link to generated config.js file + * + * This function is a wrapper for the PHP Lib (https://www.yofla.com/3d-rotate/support/plugins/php-lib-for-360-product-view/) + + * + * @param $viewData YoFLA360ViewData + * @return string + */ + public function get_iframe_url_with_generated_config_file($viewData) + { + //include library + $this->_init_rotate_tool_library(); + + $product_path_full = $this->get_full_product_path( $viewData->src ); + $product_path_relative = $this->get_relative_product_path( $viewData->src ); + + //check path + if(!file_exists($product_path_full)) + { + YoFLA360()->add_error('Path does not exist: '.$product_path_full); + return false; + } + + + //if cache attribute is stt to true, use the cache + if( $viewData->is_cache_enabled){ + Rotate_Tool::$is_cache_disabled = false; + } + + //generate config.js, always, let wordpress cache plugin do the "hard job" + $settings = Rotate_Tool::get_cascading_settings_for_directory($product_path_full); + + //generate & save config file + $config_content = Rotate_Tool::get_config_file_content($product_path_full,$settings); + + if($config_content === false) + { + YoFLA360()->add_error('Failed to generate config file for: '.$product_path_full); + return false; + } + + //enhance settings with google analytics data & with data from shortcode + $settings['ga_enabled'] = $viewData->ga_enabled; + $settings['ga_tracking_id'] = $viewData->ga_tracking_id; + $settings['ga_category'] = $viewData->ga_category; + $settings['ga_label'] = $viewData->get_ga_label(); + $settings['product_name'] = $viewData->name; + + //set "cloud" rotatetool.js, if set + if( $rotatetool_js_url = $this->get_rotatetool_js_url( $viewData ) ){ + $settings["system"]["rotatetoolUrl"] = $rotatetool_js_url; + } + + //generate iframe.html + Rotate_Tool::get_page_for_iframe($product_path_relative,$settings); + + //get iframe.html page url + $iframe_url = Rotate_Tool::get_cached_iframe_page_url($product_path_relative); + + return $iframe_url; + } + + + /** + * Returns custom rotatetool.js url, if set + * + * @param $viewData YoFLA360Viewdata + * @return string + */ + public function get_rotatetool_js_url($viewData) + { + + return $viewData->get_rotatetool_js_src(); + + } + + /** + * Ensures provided value ends with px or % + * + * @param $value + * @return string + */ + public function format_size_for_styles($value) + { + $value = trim($value); + if(substr($value, -1) == '%') return $value; + + $value = preg_replace('#[^0-9]#','',$value); + return $value.'px'; + } + + + /** + * Generates the content of the iframe source page + * + * Currently all parameters that might be overridden in the shortcode are + * passed as GET parameters. + * + * In the future, if iframe embed will be still supported, the parameters + * should be stored & read from DB + * + * @see get_iframe_url + * @return string + */ + public function construct_iframe_content() + { + //setup view data + $viewData = new YoFLA360ViewData(); + $viewData-> process_get_parameters(); + + $path = ''; + if(isset($_GET['product_url'])) $path = urldecode($_GET['product_url']); + + $path = $viewData->get_product_url(); + + //initiate google analytics event tracking values + $ga_enabled = ""; + $ga_label = ""; + $ga_category = ""; + $ga_tracking_id = ""; + + if(isset($_GET['ga_tracking_id']) && strlen($_GET['ga_tracking_id']) > 10){ + $ga_enabled = "true"; + $ga_tracking_id = (isset($_GET['ga_tracking_id']))?$_GET['ga_tracking_id']:""; + $ga_label = (isset($_GET['ga_label']))?$_GET['ga_label']:""; + $ga_category = (isset($_GET['ga_category']))?$_GET['ga_category']:""; + } + else{ + $ga_enabled = "false"; + } + + + if(isset($_GET['product_name']) ) + { + $product_name = $_GET['product_name']; + } + else + { + $product_name = '360 view'; + } + + //construct rotatetool.js path + $rotatetool_js_src = $this->get_rotatetool_js_url($viewData); + + //load template + $template_file_path = 'template-iframe.tpl'; + $template_string = file_get_contents($template_file_path); + + $values = array( + '{rotatetool_js_src}' => $rotatetool_js_src, + '{path}' => $path, + '{title}' => $product_name, + '{ga_enabled}' => $ga_enabled, + '{ga_tracking_id}' => $ga_tracking_id, + '{ga_label}' => $ga_label, + '{ga_category}' => $ga_category, + ); + $new_html = strtr($template_string,$values); + + return $new_html; + } + + /** + * Returns list of directories in specified path, along with info if they contain images for 360 views + * + * @param bool $scan_images + * @return array + */ + public function get_yofla360_directories_list($scan_images = true) + { + $path = $this->get_products_path(); + + if($scan_images) $this->_init_rotate_tool_library(); + + $directories_list = array(); + if ($handle = opendir($path)) + { + while (false !== ($file = readdir($handle))) + { + if ($file != "." && $file != "..") + { + $file_path = trailingslashit($path) . $file; + if (is_dir($file_path) && $file != YOFLA_360_TRASH_FOLDER_NAME && $file != YOFLA_360_CACHE_FOLDER_NAME) + { + if($scan_images) + { + $directories_list[] = array("name" => $file, "data" => Rotate_Tool::get_images_list($file_path)); + } + else + { + $directories_list[] = array("name" => $file); + } + } + } + } + closedir($handle); + } + return $directories_list; + } + + /** + * If paths exists, appends a number so path is unique (does not exist) + * + * Used e.g. when moving a directory to trash + * + * @param $destination + * @return string + */ + public function get_safe_destination($destination) + { + $counter = 1; + $result = $destination; + while(file_exists($result)){ + $result = $destination.'_'.$counter; + $counter++; + } + return $result; + } + + + /** + * The 3DRT Library is a PHP lib for embedding views without using the 3DRT Setup Utility + * + */ + private function _init_rotate_tool_library() + { + include_once(YOFLA_360_PLUGIN_PATH.'includes/yofla_3drt/lib/yofla/Rotate_Tool.php'); + + $system_url = plugins_url()."/360-product-rotation/includes/yofla_3drt/"; + Rotate_Tool::set_system_url($system_url); + Rotate_Tool::$products_path = $this->get_products_path(); + Rotate_Tool::$products_url = $this->get_products_url(); + Rotate_Tool::$is_cache_disabled = true; + Rotate_Tool::set_cache_url($this->get_products_url().YOFLA_360_CACHE_FOLDER_NAME); + Rotate_Tool::set_cache_path($this->get_products_path().YOFLA_360_CACHE_FOLDER_NAME); + } + + public function addHttp($url) + { + if (!preg_match("~^(?:f|ht)tps?://~i", $url)) { + $url = "http://" . $url; + } + return $url; + } + +}//class Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-black/button-pan.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/lib/yofla/get_config_file.php =================================================================== diff -u --- plugins/360-product-rotation/includes/yofla_3drt/lib/yofla/get_config_file.php (revision 0) +++ plugins/360-product-rotation/includes/yofla_3drt/lib/yofla/get_config_file.php (revision 1309) @@ -0,0 +1,8 @@ + + + + + + {title} + + + + + + + + +
+ + + \ No newline at end of file Index: plugins/360-product-rotation/includes/ajax-controller.php =================================================================== diff -u --- plugins/360-product-rotation/includes/ajax-controller.php (revision 0) +++ plugins/360-product-rotation/includes/ajax-controller.php (revision 1309) @@ -0,0 +1,58 @@ +Utils()->get_yofla360_directories_list(); + echo(json_encode($dirs)); +} + + +/** + * Moves specified 360 view folder in yofla360 folder to yofla360/trash + * + * @param $path + * @return string + */ +function move_to_trash($path){ + + if(!$path) return ''; + + //sanitize parameter (GET) + $path = basename($path); + + $y360 = YoFLA360()->Utils()->get_products_path(); + + $source = $y360.$path; + $trash = $y360.YOFLA_360_TRASH_FOLDER_NAME; + if(!file_exists($trash)) mkdir($trash); + + $target = $trash.'/'.$path; + $target = YoFLA360()->Utils()->get_safe_destination($target); + + if(!file_exists($source)) return ''; + + rename($source,$target); + + return 'ok'; +} Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-white/button-play.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/js/vendor/jquery.form.js =================================================================== diff -u --- plugins/360-product-rotation/js/vendor/jquery.form.js (revision 0) +++ plugins/360-product-rotation/js/vendor/jquery.form.js (revision 1309) @@ -0,0 +1,1271 @@ +/*! + * jQuery Form Plugin + * version: 3.51.0-2014.06.20 + * Requires jQuery v1.5 or later + * Copyright (c) 2014 M. Alsup + * Examples and documentation at: http://malsup.com/jquery/form/ + * Project repository: https://github.com/malsup/form + * Dual licensed under the MIT and GPL licenses. + * https://github.com/malsup/form#copyright-and-license + */ +/*global ActiveXObject */ + +// AMD support +(function (factory) { + "use strict"; + if (typeof define === 'function' && define.amd) { + // using AMD; register as anon module + define(['jquery'], factory); + } else { + // no AMD; invoke directly + factory( (typeof(jQuery) != 'undefined') ? jQuery : window.Zepto ); + } +} + +(function($) { + "use strict"; + + /* + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are mutually exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + $(document).ready(function() { + $('#myForm').on('submit', function(e) { + e.preventDefault(); // <-- important + $(this).ajaxSubmit({ + target: '#output' + }); + }); + }); + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + You can also use ajaxForm with delegation (requires jQuery v1.7+), so the + form does not have to exist when you invoke ajaxForm: + $('#myForm').ajaxForm({ + delegation: true, + target: '#output' + }); + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. + */ + + /** + * Feature detection + */ + var feature = {}; + feature.fileapi = $("").get(0).files !== undefined; + feature.formdata = window.FormData !== undefined; + + var hasProp = !!$.fn.prop; + +// attr2 uses prop when it can but checks the return type for +// an expected string. this accounts for the case where a form +// contains inputs with names like "action" or "method"; in those +// cases "prop" returns the element + $.fn.attr2 = function() { + if ( ! hasProp ) { + return this.attr.apply(this, arguments); + } + var val = this.prop.apply(this, arguments); + if ( ( val && val.jquery ) || typeof val === 'string' ) { + return val; + } + return this.attr.apply(this, arguments); + }; + + /** + * ajaxSubmit() provides a mechanism for immediately submitting + * an HTML form using AJAX. + */ + $.fn.ajaxSubmit = function(options) { + /*jshint scripturl:true */ + + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log('ajaxSubmit: skipping submit process - no element selected'); + return this; + } + + var method, action, url, $form = this; + + if (typeof options == 'function') { + options = { success: options }; + } + else if ( options === undefined ) { + options = {}; + } + + method = options.type || this.attr2('method'); + action = options.url || this.attr2('action'); + + url = (typeof action === 'string') ? $.trim(action) : ''; + url = url || window.location.href || ''; + if (url) { + // clean url (don't include hash vaue) + url = (url.match(/^([^#]+)/)||[])[1]; + } + + options = $.extend(true, { + url: url, + success: $.ajaxSettings.success, + type: method || $.ajaxSettings.type, + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' + }, options); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); + return this; + } + + // provide opportunity to alter form data before it is serialized + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSerialize callback'); + return this; + } + + var traditional = options.traditional; + if ( traditional === undefined ) { + traditional = $.ajaxSettings.traditional; + } + + var elements = []; + var qx, a = this.formToArray(options.semantic, elements); + if (options.data) { + options.extraData = options.data; + qx = $.param(options.data, traditional); + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSubmit callback'); + return this; + } + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); + return this; + } + + var q = $.param(a, traditional); + if (qx) { + q = ( q ? (q + '&' + qx) : qx ); + } + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else { + options.data = q; // data is the query string for 'post' + } + + var callbacks = []; + if (options.resetForm) { + callbacks.push(function() { $form.resetForm(); }); + } + if (options.clearForm) { + callbacks.push(function() { $form.clearForm(options.includeHidden); }); + } + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + var fn = options.replaceTarget ? 'replaceWith' : 'html'; + $(options.target)[fn](data).each(oldSuccess, arguments); + }); + } + else if (options.success) { + callbacks.push(options.success); + } + + options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg + var context = options.context || this ; // jQuery 1.4+ supports scope context + for (var i=0, max=callbacks.length; i < max; i++) { + callbacks[i].apply(context, [data, status, xhr || $form, $form]); + } + }; + + if (options.error) { + var oldError = options.error; + options.error = function(xhr, status, error) { + var context = options.context || this; + oldError.apply(context, [xhr, status, error, $form]); + }; + } + + if (options.complete) { + var oldComplete = options.complete; + options.complete = function(xhr, status) { + var context = options.context || this; + oldComplete.apply(context, [xhr, status, $form]); + }; + } + + // are there files to upload? + + // [value] (issue #113), also see comment: + // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219 + var fileInputs = $('input[type=file]:enabled', this).filter(function() { return $(this).val() !== ''; }); + + var hasFileInputs = fileInputs.length > 0; + var mp = 'multipart/form-data'; + var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); + + var fileAPI = feature.fileapi && feature.formdata; + log("fileAPI :" + fileAPI); + var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI; + + var jqxhr; + + // options.iframe allows user to force iframe mode + // 06-NOV-09: now defaulting to iframe mode if file input is detected + if (options.iframe !== false && (options.iframe || shouldUseFrame)) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) { + $.get(options.closeKeepAlive, function() { + jqxhr = fileUploadIframe(a); + }); + } + else { + jqxhr = fileUploadIframe(a); + } + } + else if ((hasFileInputs || multipart) && fileAPI) { + jqxhr = fileUploadXhr(a); + } + else { + jqxhr = $.ajax(options); + } + + $form.removeData('jqxhr').data('jqxhr', jqxhr); + + // clear element array + for (var k=0; k < elements.length; k++) { + elements[k] = null; + } + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + // utility fn for deep serialization + function deepSerialize(extraData){ + var serialized = $.param(extraData, options.traditional).split('&'); + var len = serialized.length; + var result = []; + var i, part; + for (i=0; i < len; i++) { + // #252; undo param space replacement + serialized[i] = serialized[i].replace(/\+/g,' '); + part = serialized[i].split('='); + // #278; use array instead of object storage, favoring array serializations + result.push([decodeURIComponent(part[0]), decodeURIComponent(part[1])]); + } + return result; + } + + // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz) + function fileUploadXhr(a) { + var formdata = new FormData(); + + for (var i=0; i < a.length; i++) { + formdata.append(a[i].name, a[i].value); + } + + if (options.extraData) { + var serializedData = deepSerialize(options.extraData); + for (i=0; i < serializedData.length; i++) { + if (serializedData[i]) { + formdata.append(serializedData[i][0], serializedData[i][1]); + } + } + } + + options.data = null; + + var s = $.extend(true, {}, $.ajaxSettings, options, { + contentType: false, + processData: false, + cache: false, + type: method || 'POST' + }); + + if (options.uploadProgress) { + // workaround because jqXHR does not expose upload property + s.xhr = function() { + var xhr = $.ajaxSettings.xhr(); + if (xhr.upload) { + xhr.upload.addEventListener('progress', function(event) { + var percent = 0; + var position = event.loaded || event.position; /*event.position is deprecated*/ + var total = event.total; + if (event.lengthComputable) { + percent = Math.ceil(position / total * 100); + } + options.uploadProgress(event, position, total, percent); + }, false); + } + return xhr; + }; + } + + s.data = null; + var beforeSend = s.beforeSend; + s.beforeSend = function(xhr, o) { + //Send FormData() provided by user + if (options.formData) { + o.data = options.formData; + } + else { + o.data = formdata; + } + if(beforeSend) { + beforeSend.call(this, xhr, o); + } + }; + return $.ajax(s); + } + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUploadIframe(a) { + var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle; + var deferred = $.Deferred(); + + // #341 + deferred.abort = function(status) { + xhr.abort(status); + }; + + if (a) { + // ensure that every serialized input is still enabled + for (i=0; i < elements.length; i++) { + el = $(elements[i]); + if ( hasProp ) { + el.prop('disabled', false); + } + else { + el.removeAttr('disabled'); + } + } + } + + s = $.extend(true, {}, $.ajaxSettings, options); + s.context = s.context || s; + id = 'jqFormIO' + (new Date().getTime()); + if (s.iframeTarget) { + $io = $(s.iframeTarget); + n = $io.attr2('name'); + if (!n) { + $io.attr2('name', id); + } + else { + id = n; + } + } + else { + $io = $(' + \ No newline at end of file Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-black/zoomslider-bar.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/pure-white/button-fullscreen.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/styles/add-media.css =================================================================== diff -u --- plugins/360-product-rotation/styles/add-media.css (revision 0) +++ plugins/360-product-rotation/styles/add-media.css (revision 1309) @@ -0,0 +1,120 @@ +/** + * Upload 360 view (add media) plugin page styles + */ + + +/** + * Wrapper for the elements on the page + */ +#yofla360_plugin_media_wrapper{ + height: 100%; +} + +/** + * Upload Section + */ +#yofla360_plugin_media_upload{ + +} + + +#upload-wrapper { + width: 90%; + background: #ddd; + border-radius: 10px; + padding: 10px; + margin-top: 5px; +} + +#upload-wrapper input[type=file] { + padding: 6px; + background: #FFF; + border-radius: 5px; + line-height: 16px; + min-width: 300px; +} +#upload-wrapper #submit-btn { + border: none; + padding: 10px; + background: #61BAE4; + border-radius: 5px; + color: #FFF; + margin-left: 10px; +} +#output{ + padding: 5px; + font-size: 12px; +} + +/* prograss bar */ +#progressbox { + border: 1px solid #61BAE4; + padding: 1px; + position:relative; + width: 100%; + max-width: 365px; + border-radius: 3px; + margin-top: 5px; + display:none; + text-align:left; +} + +#progressbar { + height:20px; + border-radius: 3px; + background-color: #0073aa; + width:1%; + color: white; + +} + +#statustxt { + top:3px; + left:50%; + position:absolute; + display:inline-block; + color: #FFFFFF; +} + +/** + * List section + */ +#yofla360_plugin_media_list{ + +} + +ul.products_list { + margin-left: 10px; +} + +ul.products_list li{ + list-style: disc; + margin-left: 10px; + font-size: 16px !important; + font-weight: bold; + line-height: 25px; +} + +ul.products_list li.invalid{ + list-style: square; + margin-left: 10px; + color: #444 !important; + font-weight: normal; +} + + +ul.products_list li span.action{ + text-decoration: underline; + font-size: 13px; + font-weight: normal; + cursor: pointer; +} + + +ul.products_list li span.invalid{ + font-size: 13px; + font-weight: normal; +} + + + Index: plugins/360-product-rotation/includes/yofla_3drt/themes/pure-white/button-play.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-white/button-down.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/pure-white/button-pan.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-black/button-up.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default/styles.css =================================================================== diff -u --- plugins/360-product-rotation/includes/yofla_3drt/themes/default/styles.css (revision 0) +++ plugins/360-product-rotation/includes/yofla_3drt/themes/default/styles.css (revision 1309) @@ -0,0 +1,202 @@ +/** + * Rotate Tool Player Styles + * + * Author Matus Laco, http://www.yofla.com/3d-rotate/ + * + * For player version 1.6.2 and above + * + * @updated 2015-06-01 + * + */ + +div.rotate-tool-instance{ +} + + +div.rotate-tool-button +{ + width: 22px; + height: 22px; + cursor: pointer; + background-repeat: no-repeat; + background-size: 44px 22px; +} + +div.rotate-tool-button:hover +{ + background-position: -22px; +} + +div.rotate-tool-button-start{ + margin-left: 38%; + top: 40%; + width: 120px; + height: 60px; + cursor: pointer; + background-repeat: no-repeat; + background-size: 240px 60px; + background-image: url(button-start.png); +} +div.rotate-tool-button.rotate-tool-button-start:hover +{ + background-position: -120px; +} + + +div.rotate-tool-button-play{ + left: 5px; + bottom: 5px; + background-image: url(button-play.png); +} + +div.rotate-tool-button-left{ + left: 32px; + bottom: 5px; + background-image: url(button-left.png); +} + +div.rotate-tool-button-right{ + left: 59px; + bottom: 5px; + background-image: url(button-right.png); +} + +div.rotate-tool-button-up{ + left: 86px; + bottom: 5px; + background-image: url(button-up.png); +} + +div.rotate-tool-button-down{ + left: 113px; + bottom: 5px; + background-image: url(button-down.png); +} + +div.rotate-tool-button-zoom-in{ + left: 143px; + bottom: 5px; + background-image: url(button-zoom-in.png); +} + +div.rotate-tool-button-zoom-out{ + left: 170px; + bottom: 5px; + background-image: url(button-zoom-out.png); +} + +div.rotate-tool-button-fullscreen{ + right: 5px; + bottom: 5px; + background-image: url(button-fullscreen.png); +} + + +div.rotate-tool-button-fullscreen{ + right: 5px; + bottom: 5px; + background-image: url(button-fullscreen.png); +} + +div.rotate-tool-zoomslider{ + width: 110px; + height: 22px; + left: 197px; + bottom: 5px; + background-image: url(zoomslider-bar.png); + background-repeat: no-repeat; +} + +div.rotate-tool-zoomslider-handle{ + width: 13px; + height: 22px; + left: 0px; + bottom: 0px; + background-image: url(zoomslider-handle.png); + background-repeat: no-repeat; + background-size: 26px 22px; +} + +div.rotate-tool-button-toggle-rotate{ + left: 314px; + bottom: 5px; + background-image: url(button-rotate.png); +} + +div.rotate-tool-button-toggle-pan{ + left: 341px; + bottom: 5px; + background-image: url(button-pan.png); +} + + +div.rotate-tool-zoomslider-handle:hover{ + background-position: -13px; +} + + +div.rotate-tool-hotspot-default{ + width: 22px; + height: 22px; + border-radius: 14px; + border: 2px solid white; + opacity: 0.75; + filter: Alpha(Opacity=75); + background-color: deepskyblue; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAAMElEQVRIx2NgGAWDAvz//3/mfwTYPWrwIDUYqDHtP+lg5qjBxBs8mtyGmcGjgCIAAG/Yc/D2udsVAAAAAElFTkSuQmCC); +} + +/** + * The wrapper div for the Image Title for the Image Hotspot + */ +div.rotate-tool-image-hotspot-title{ + background-color : #000000; + position: absolute; + left: 0px; + bottom: 0px; + padding : 10px; + opacity : 0.75; +} + +/** + * The Title of the Image Hotspot is written inside of this div. + */ +div.rotate-tool-image-hotspot-title-inner{ + font-family : sans-serif; + color : white; + font-size : 18px; + text-align : left; +} + + + +@media screen and (max-width: 380px) { + div.rotate-tool-button-right{ + left: 53px; + } + + div.rotate-tool-button-zoom-in{ + left: 80px; + } + + div.rotate-tool-button-zoom-out{ + left: 101px; + } + + div.rotate-tool-zoomslider{ + left: 130px; + } + + div.rotate-tool-button-toggle-rotate{ + right: 5px; + bottom: 54px; + left: auto; + } + + div.rotate-tool-button-toggle-pan{ + right: 5px; + bottom: 33px; + left: auto; + } +} + Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-white/button-up.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-black/button-zoom-in.png =================================================================== diff -u Binary files differ Index: plugins/360-product-rotation/360-product-rotation.php =================================================================== diff -u --- plugins/360-product-rotation/360-product-rotation.php (revision 0) +++ plugins/360-product-rotation/360-product-rotation.php (revision 1309) @@ -0,0 +1,166 @@ +_errors = array(); + + if (in_array("woocommerce/woocommerce.php", apply_filters("active_plugins", get_option("active_plugins")))) + { + include_once YOFLA_360_PLUGIN_PATH.'includes/class-yofla360-woocommerce.php'; + new YoFLA360Woocommerce(); + } + + //includes + include_once YOFLA_360_PLUGIN_PATH.'includes/class-yofla360-utils.php'; + + //in wordpress admin area + if (is_admin()) + { + include_once YOFLA_360_PLUGIN_PATH.'includes/class-yofla360-activation.php'; + include_once YOFLA_360_PLUGIN_PATH.'includes/class-yofla360-settings.php'; + include_once YOFLA_360_PLUGIN_PATH.'includes/class-yofla360-addmedia.php'; + + new YoFLA360Activation(); + new YoFLA360Settings(); + new YoFLA360Addmedia(); + + $this->checkUpgrading(); + + } + //in wordpress frontend + else + { + include_once YOFLA_360_PLUGIN_PATH.'includes/class-yofla360-shortcodes.php'; + include_once YOFLA_360_PLUGIN_PATH.'includes/class-yofla360-frontend.php'; + include_once YOFLA_360_PLUGIN_PATH.'includes/class-yofla360-viewdata.php'; + + //init shortcodes + new YoFLA360Shortcodes(); + } + + //in wordpress admin or on wordpress frontend + + } + + private function checkUpgrading() + { + //check if upgrading... + if (get_option(YOFLA_360_VERSION_KEY) != YOFLA_360_VERSION_NUM) { + // Execute your upgrade logic here + // no action + // Update version + update_option(YOFLA_360_VERSION_KEY, YOFLA_360_VERSION_NUM); + } + } + + /** + * Adds an error + * + * @param $msg + */ + public function add_error($msg) + { + $this->_errors[] = $msg; + } + + + /** + * Return errors, if any + */ + public function get_errors() + { + if( sizeof($this->_errors) > 0) + { + return YoFLA360()->Frontend()->format_error( implode('
'.PHP_EOL, $this->_errors) ); + } + else + { + return false; + } + } + + + /** + * Handle for Utils functions + * + * @return YoFLA360Frontend + */ + public function Utils() + { + return YoFLA360Utils::instance(); + } + + /** + * Handle for Frontend functions + * + * @return YoFLA360Frontend + */ + public function Frontend() + { + return YoFLA360Frontend::instance(); + } +} + +/** + * Returns main instance of YoFLA360 class + * + * @return YoFLA360 + */ +function YoFLA360(){ + return YoFLA360::instance(); +} + +//initialize +YoFLA360(); + + Index: plugins/360-product-rotation/js/add-media.js =================================================================== diff -u --- plugins/360-product-rotation/js/add-media.js (revision 0) +++ plugins/360-product-rotation/js/add-media.js (revision 1309) @@ -0,0 +1,232 @@ +/** + * Javascript for managing the add 360 view media page + * + */ + + +/** + * Namespace for yofla upload related functions + * + */ +var yupload = {}; + +//reference to jQuery +yupload.$ = $ || jQuery; + +/** + * + * + * Based on: http://www.sanwebe.com/2012/06/ajax-file-upload-with-php-and-jquery + * + * @returns {boolean} + */ +yupload.beforeSubmit = function(){ + //check whether client browser fully supports all File API + if (window.File && window.FileReader && window.FileList && window.Blob) { + + if(!yupload.$('#FileInput')[0].files[0]){ + yupload.$("#output").html("Please select a file!"); + return false; + } + + var ftype = yupload.$('#FileInput')[0].files[0].type; // get file type + var fsize = yupload.$('#FileInput')[0].files[0].size; //get file size + + //allow file types + /* not working reliable... check disabled + switch(ftype) { + case 'application/x-zip-compressed': + case 'application/zip': + //all ok + break; + default: + yupload.$("#output").html(""+ftype+" Unsupported file type!"); + return false + } + */ + + maxAllowedUploadSize = maxAllowedUploadSize || (10*1048576); //defined variable from php script or fallback + + //Allowed file size is less than 5 MB (1048576) + if(fsize > maxAllowedUploadSize ) + { + yupload.$("#output").html(""+bytesToSize(fsize) +" Too big file!
File is too big, it should be less than " + bytesToSize(maxAllowedUploadSize) +"."); + return false + } + + yupload.$('#submit-btn').hide(); //hide submit button + yupload.$('#loading-img').show(); //hide submit button + yupload.$("#output").html(""); + } + else { + alert("Please upgrade your browser, because your current browser lacks some new features needed for this file upload!"); + } +}; + +yupload.afterSuccess = function(){ + yupload.$('#submit-btn').show(); //show submit button + yupload.$('#loading-img').hide(); //hide submit button + yupload.$('#progressbox').delay( 1000 ).fadeOut(); //hide progress bar + yupload.$('#statustxt').text('Upload successful.'); //update status text + yupload.load360ViewsList(); +}; + +yupload.onProgress = function(event, position, total, percentComplete){ + //Progress bar + yupload.$('#progressbox').show(); + yupload.$('#progressbar').width(percentComplete + '%') //update progressbar percent complete + yupload.$('#statustxt').html(percentComplete + '%'); //update status text + if(percentComplete>50) + { + yupload.$('#statustxt').css('color','#000'); //change status text to white after 50% + } +}; + + +yupload.initUploader = function(){ + var options = { + target: '#output', // target element(s) to be updated with server response + beforeSubmit: yupload.beforeSubmit, // pre-submit callback + success: yupload.afterSuccess, // post-submit callback + uploadProgress: yupload.onProgress, //upload progress callback + resetForm: true // reset the form after successful submit + }; + + yupload.$('#MyUploadForm').submit(function() { + yupload.$(this).ajaxSubmit(options); + return false; + }); + + yupload.$('#FileInput').on("change", function(){ + yupload.$('#MyUploadForm').ajaxSubmit(options); + }); + +}; + + +/** + * Listeners lists the files in the 360 folder + */ +yupload.load360ViewsList = function(){ + + yupload.$('ul.products_list').text('Loading...'); + + var rnd = Math.round(Math.random()*1000); + + var utilsUrl = yofla360PluginUrl + "includes/ajax-controller.php?action=y360list&rnd="+rnd ; + + var jqxhr = yupload.$.ajax(utilsUrl) + .done(function(data) { + data = JSON.parse(data); + if( data && data.length > 0 ){ + yupload.display360ViewsList(data); + yupload.addCopyEmbedCodeActions(); + } + else{ + yupload.$('ul.products_list').html('No 360° views were uploaded yet.'); + } + }) + .fail(function() { + yupload.$('ul.products_list').text( "Failed loading the list of 360 views!" ); + }) + .always(function() { + + }); +}; + +/** + * Renders the html code that lists the 360 views and updates the + * HTML of the page + * + * @param data + */ +yupload.display360ViewsList = function(data) { + var output = '', cssClass, action, name, valid, dataPath, item, invalid_text, action_text; + + for(var i=0; i 0); + dataPath = name; + + invalid_text = '(not a 360° view folder)'; + action_text = 'embed it, trash it'; + + cssClass += (valid) ? 'valid' : 'invalid'; + action += (valid) ? action_text : invalid_text; + output += "
  • "+name+" "+action+"
  • \n"; + } + + yupload.$('ul.products_list').html(output); +} + +yupload.addCopyEmbedCodeActions = function(){ + yupload.$('span.action_embed').click(function(){ + var data = yupload.$(this).data(); + var path = 'yofla360/'+data.path; + + var text = '[360 width="100%" height="400px" src="'+path+'"]'; + + //copy to clipboard + var msg = "Copy the shortcode to clipboard and then paste into any page.\n"; + msg += "(you can modify the width/height parameter as you like)"; + window.prompt(msg, text); + + }); + + yupload.$('span.action_trash').click(function(){ + var data = yupload.$(this).data(); + var path = data.path; + + var doTrash = confirm("Move \""+path+"\" to trash?\n\nYou can recover it from trash or delete permanently using FTP.") + if(doTrash){ + yupload.moveToTrash(path); + } + }); +} + +/** + * Moves 360 view folder to trash + * + * @param path + */ +yupload.moveToTrash = function(path){ + + var url = yofla360PluginUrl + "includes/ajax-controller.php?action=trash&path="+encodeURIComponent(path); + + yupload.$('ul.products_list').text('Processing...'); + + var jqxhr = yupload.$.ajax(url) + .done(function(data) { + }) + .fail(function() { + }) + .always(function() { + yupload.load360ViewsList(); + }); +} + + +/** + * function to format bites bit.ly/19yoIPO + * + * @param bytes + * @returns {string} + */ +function bytesToSize(bytes) { + var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + if (bytes == 0) return '0 Bytes'; + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); + return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]; +} + +/** + * Entry point + */ +yupload.$(document).ready(function() { + yupload.initUploader(); + yupload.load360ViewsList(); +}); + Index: plugins/360-product-rotation/includes/yofla_3drt/themes/default-black/button-right.png =================================================================== diff -u Binary files differ