From 80ff48b655f3207f89e9ac0eaa2a984b2aee2485 Mon Sep 17 00:00:00 2001 From: vnmedeiros Date: Wed, 3 Oct 2018 10:26:54 -0300 Subject: [PATCH] initial struct for exporters --- ...ass-tainacan-rest-exporters-controller.php | 206 +++++ src/api/tainacan-rest-creator.php | 1 + src/classes/tainacan-creator.php | 89 +- src/exporter/class-tainacan-bg-exporter.php | 48 + src/exporter/class-tainacan-csv.php | 26 + .../class-tainacan-export-handler.php | 101 ++ src/exporter/class-tainacan-exporter.php | 868 ++++++++++++++++++ 7 files changed, 1297 insertions(+), 42 deletions(-) create mode 100644 src/api/endpoints/class-tainacan-rest-exporters-controller.php create mode 100644 src/exporter/class-tainacan-bg-exporter.php create mode 100644 src/exporter/class-tainacan-csv.php create mode 100644 src/exporter/class-tainacan-export-handler.php create mode 100644 src/exporter/class-tainacan-exporter.php diff --git a/src/api/endpoints/class-tainacan-rest-exporters-controller.php b/src/api/endpoints/class-tainacan-rest-exporters-controller.php new file mode 100644 index 000000000..63f94593a --- /dev/null +++ b/src/api/endpoints/class-tainacan-rest-exporters-controller.php @@ -0,0 +1,206 @@ +rest_base = 'exporters'; + if (session_status() == PHP_SESSION_NONE) { + @session_start(); // @ avoids Warnings when running phpunit tests + } + parent::__construct(); + } + + + /** + * Register the collections route and their endpoints + */ + public function register_routes() { + register_rest_route($this->namespace, '/' . $this->rest_base . '/available', array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_registered_exporters'), + 'permission_callback' => array($this, 'export_permissions_check'), + ), + )); + + register_rest_route($this->namespace, '/' . $this->rest_base . '/session', array( + array( + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => array($this, 'create_item'), + 'permission_callback' => array($this, 'export_permissions_check'), + 'args' => [ + 'importer_slug' => [ + 'type' => 'string', + 'description' => __( 'The slug of the exporter to be initialized', 'tainacan' ), + ] + ], + ), + )); + + register_rest_route($this->namespace, '/' . $this->rest_base . '/session/(?P[0-9a-f]+)', array( + array( + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => array($this, 'update_item'), + 'permission_callback' => array($this, 'export_permissions_check'), + 'args' => [ + 'send_email' => [ + 'type' => 'string', + 'description' => __( 'The e-mail to be used by the export to send a message when the process ends', 'tainacan' ), + ], + 'collection' => [ + 'type' => 'array/object', + 'description' => __( 'The array describing the collection as expected by the exporter', 'tainacan' ), + ], + 'options' => [ + 'type' => 'array/object', + 'description' => __( 'The importer options', 'tainacan' ), + ] + ], + ), + + )); + } + + /** + * + * @param \WP_REST_Request $request + * + * @return bool|\WP_Error + * @throws \Exception + */ + public function export_permissions_check($request) { + return true; + } + + public function run($request) { + $session_id = $request['session_id']; + $exporter = $_SESSION['tainacan_importer'][$session_id]; + + if(!$exporter) { + return new \WP_REST_Response([ + 'error_message' => __('Exporter Session not found', 'tainacan' ), + 'session_id' => $session_id + ], 400); + } + + global $Tainacan_Exporter_Handler; + $process = $Tainacan_Exporter_Handler->add_to_queue($exporter); + + if (false === $process) { + return new \WP_REST_Response([ + 'error_message' => __('Error starting importer', 'tainacan' ), + 'session_id' => $session_id + ], 400); + } + + $response = [ + 'bg_process_id' => $process->ID + ]; + return new \WP_REST_Response( $response, 200 ); + + } + + public function get_registered_exporters() { + global $Tainacan_Exporter_Handler; + $exporters = $Tainacan_Exporter_Handler->get_registered_exporters(); + return new \WP_REST_Response( $exporters, 200 ); + } + + /** + * Creates a new instance of the desired exporter and returns its ID + * + * @param \WP_REST_Request $request + * + * @return array|\WP_Error|\WP_REST_Response + */ + public function create_item( $request ) { + $body = json_decode($request->get_body(), true); + + if(empty($body)) { + return new \WP_REST_Response([ + 'error_message' => __('Body can not be empty.', 'tainacan'), + ], 400); + } + $slug = $body['exporter_slug']; + global $Tainacan_Exporter_Handler; + + if ($object = $Tainacan_Exporter_Handler->initialize_exporter($slug)) { + $response = $object->_to_Array(); + return new \WP_REST_Response($response, 201); + } else { + return new \WP_REST_Response([ + 'error_message' => __('Exporter not found', 'tainacan'), + ], 400); + } + } + + /** + * Update a collection + * + * @param \WP_REST_Request $request + * + * @return string|\WP_Error|\WP_REST_Response + */ + public function update_item( $request ) { + + $session_id = $request['session_id']; + $body = json_decode($request->get_body(), true); + + if(!empty($body)) { + $attributes = []; + foreach ($body as $att => $value) { + $attributes[$att] = $value; + } + + $importer = $_SESSION['tainacan_exporter'][$session_id]; + if($importer) { + foreach ($body as $att => $value) { + if ($att == 'collection') { + if (is_array($value) && isset($value['id'])) { + $importer->add_collection($value); + continue; + } else { + return new \WP_REST_Response([ + 'error_message' => __('Invalid collection', 'tainacan' ), + 'session_id' => $session_id + ], 400); + } + } + $method = 'set_' . $att; + if (method_exists($importer, $method)) { + $importer->$method($value); + } + } + + $response = $importer->_to_Array(); + return new \WP_REST_Response( $response, 200 ); + } + + return new \WP_REST_Response([ + 'error_message' => __('Importer Session not found', 'tainacan' ), + 'session_id' => $session_id + ], 400); + } + + return new \WP_REST_Response([ + 'error_message' => __('The body can not be empty', 'tainacan'), + 'body' => $body + ], 400); + } +} + +?> diff --git a/src/api/tainacan-rest-creator.php b/src/api/tainacan-rest-creator.php index 8cb8b302d..d70331e3b 100644 --- a/src/api/tainacan-rest-creator.php +++ b/src/api/tainacan-rest-creator.php @@ -14,6 +14,7 @@ $rest_logs_controller = new \Tainacan\API\EndPoints\REST_Logs_Control $rest_metadata_types_controller = new \Tainacan\API\EndPoints\REST_Metadata_Types_Controller(); $rest_filter_types_controller = new \Tainacan\API\EndPoints\REST_Filter_Types_Controller(); $rest_importers_controller = new \Tainacan\API\EndPoints\REST_Importers_Controller(); +$rest_exporters_controller = new \Tainacan\API\EndPoints\REST_Exporters_Controller(); $rest_background_processes_controller = new \Tainacan\API\EndPoints\REST_Background_Processes_Controller(); $rest_bulkedit_controller = new \Tainacan\API\EndPoints\REST_Bulkedit_Controller(); new \Tainacan\API\EndPoints\REST_Export_Controller(); diff --git a/src/classes/tainacan-creator.php b/src/classes/tainacan-creator.php index 5b2e4f688..41f94db74 100644 --- a/src/classes/tainacan-creator.php +++ b/src/classes/tainacan-creator.php @@ -9,6 +9,7 @@ const TAINACAN_VENDOR_DIR = __DIR__ . '/../vendor/'; const TAINACAN_TAPI_DIR = __DIR__ . '/../api/'; const TAINACAN_ENDPOINTS_DIR = __DIR__ . '/../api/endpoints/'; const TAINACAN_IMPORTER_DIR = __DIR__ . '/../importer/'; +const TAINACAN_EXPORTER_DIR = __DIR__ . '/../exporter/'; const TAINACAN_EXPOSERS_DIR = __DIR__ . '/../exposers/'; const DIRS = [ @@ -21,6 +22,7 @@ const DIRS = [ TAINACAN_TAPI_DIR, TAINACAN_ENDPOINTS_DIR, TAINACAN_IMPORTER_DIR, + TAINACAN_EXPORTER_DIR, TAINACAN_EXPOSERS_DIR ]; @@ -35,62 +37,65 @@ require_once(TAINACAN_IMPORTER_DIR . 'class-tainacan-importer.php'); require_once(TAINACAN_IMPORTER_DIR . 'class-tainacan-importer-handler.php'); require_once(TAINACAN_EXPOSERS_DIR . 'class-tainacan-exposers.php'); +require_once(TAINACAN_EXPORTER_DIR . 'class-tainacan-bg-exporter.php'); +require_once(TAINACAN_EXPORTER_DIR . 'class-tainacan-export-handler.php'); spl_autoload_register('tainacan_autoload'); function tainacan_autoload($class_name){ - $class_path = explode('\\', $class_name); - $class_name = end($class_path); + $class_path = explode('\\', $class_name); + $class_name = end($class_path); - if(count($class_path) == 1 ) { - foreach(DIRS as $dir) { - $file = $dir . 'class-'. strtolower(str_replace('_', '-' , $class_name)) . '.php'; + if(count($class_path) == 1 ) { + foreach(DIRS as $dir) { + $file = $dir . 'class-'. strtolower(str_replace('_', '-' , $class_name)) . '.php'; - if(file_exists($file)) { - require_once($file); - } - } - } - elseif ($class_path[0] == 'Tainacan') { - $sliced = array_slice($class_path, 1, count($class_path) -2); - - if( isset( $class_path[1] ) && $class_path[1] === 'Importer' ){ + if(file_exists($file)) { + require_once($file); + } + } + } + elseif ($class_path[0] == 'Tainacan') { + $sliced = array_slice($class_path, 1, count($class_path) -2); + if( isset( $class_path[1] ) && $class_path[1] === 'Importer' ) { $dir = TAINACAN_IMPORTER_DIR; $dir_import = strtolower(str_replace('_', '-' , $class_name)); if (file_exists("$dir$dir_import/")) { $dir .= "$dir_import/"; } - } else if( isset( $class_path[1] ) && $class_path[1] === 'Exposers' ){ - $dir = TAINACAN_EXPOSERS_DIR; - if(count($class_path) > 3) $dir .= strtolower($class_path[2]).DIRECTORY_SEPARATOR; - } else if( isset( $class_path[1] ) && $class_path[1] === 'API' ){ - $dir = TAINACAN_TAPI_DIR; - if(count($class_path) > 3) $dir .= strtolower($class_path[2]).DIRECTORY_SEPARATOR; - } else if($sliced) { - $lower = $sliced[0]; - $sliced[0] = strtolower( $lower ); + } else if( isset( $class_path[1] ) && $class_path[1] === 'Exporter' ) { + $dir = TAINACAN_EXPORTER_DIR; + } else if( isset( $class_path[1] ) && $class_path[1] === 'Exposers' ){ + $dir = TAINACAN_EXPOSERS_DIR; + if(count($class_path) > 3) $dir .= strtolower($class_path[2]).DIRECTORY_SEPARATOR; + } else if( isset( $class_path[1] ) && $class_path[1] === 'API' ){ + $dir = TAINACAN_TAPI_DIR; + if(count($class_path) > 3) $dir .= strtolower($class_path[2]).DIRECTORY_SEPARATOR; + } else if($sliced) { + $lower = $sliced[0]; + $sliced[0] = strtolower( $lower ); - $dir = implode( DIRECTORY_SEPARATOR, $sliced ) . DIRECTORY_SEPARATOR; - $dir = TAINACAN_CLASSES_DIR . str_replace( '_', '-', $dir ); - } else { - $dir = TAINACAN_CLASSES_DIR; - } + $dir = implode( DIRECTORY_SEPARATOR, $sliced ) . DIRECTORY_SEPARATOR; + $dir = TAINACAN_CLASSES_DIR . str_replace( '_', '-', $dir ); + } else { + $dir = TAINACAN_CLASSES_DIR; + } - if( in_array('Metadata_Types', $class_path) || in_array('Filter_Types', $class_path) ){ - $exceptions = ['taxonomytaginput','taxonomycheckbox']; - if( in_array( strtolower( $class_name ), $exceptions) ){ - $dir.= 'taxonomy/'; - }else{ - $dir.= strtolower(str_replace('_', '-' , $class_name)).'/'; - } - } + if( in_array('Metadata_Types', $class_path) || in_array('Filter_Types', $class_path) ){ + $exceptions = ['taxonomytaginput','taxonomycheckbox']; + if( in_array( strtolower( $class_name ), $exceptions) ){ + $dir.= 'taxonomy/'; + }else{ + $dir.= strtolower(str_replace('_', '-' , $class_name)).'/'; + } + } - $file = $dir . 'class-tainacan-'. strtolower(str_replace('_', '-' , $class_name)) . '.php'; - - if(file_exists($file)) { - require_once($file); - } - } + $file = $dir . 'class-tainacan-'. strtolower(str_replace('_', '-' , $class_name)) . '.php'; + + if(file_exists($file)) { + require_once($file); + } + } } $Tainacan_Collections = \Tainacan\Repositories\Collections::get_instance(); diff --git a/src/exporter/class-tainacan-bg-exporter.php b/src/exporter/class-tainacan-bg-exporter.php new file mode 100644 index 000000000..b315ec41d --- /dev/null +++ b/src/exporter/class-tainacan-bg-exporter.php @@ -0,0 +1,48 @@ +set_name( __('Exporter', 'tainacan') ); + } + + function task($batch) { + + $data = $batch->data; + $key = $batch->key; + + $className = $data['class_name']; + if (class_exists($className)) { + $object = new $className($data); + $runned = $object->run(); + + $this->write_log($key, $object->get_log()); + $this->write_error_log($key, $object->get_error_log()); + + if (true === $object->get_abort()) { + throw new \Exception('Process aborted by Importer'); + } + + if (false === $runned) { + return false; + } + + $batch->progress_label = $object->get_progress_label(); + $batch->progress_value = $object->get_progress_value(); + + $batch->data = $object->_to_Array(true); + + return $batch; + } + return false; + } +} + ?> \ No newline at end of file diff --git a/src/exporter/class-tainacan-csv.php b/src/exporter/class-tainacan-csv.php new file mode 100644 index 000000000..ed22b8978 --- /dev/null +++ b/src/exporter/class-tainacan-csv.php @@ -0,0 +1,26 @@ +add_error_log('passou aqui!'); + return false; + } + + public function options_form() { + ob_start(); + ?> +
+

Priemiro teste da construção de um Exporter!

+
+ bg_exporter = new Background_Exporter(); + add_action('init', array(&$this, 'init')); + } + + public function init() { + + $this->register_exporter([ + 'name' => 'CSV collections', + 'description' => __('Exporter', 'tainacan'), + 'slug' => 'csv', + 'class_name' => '\Tainacan\Exporter\CSV' + ]); + + do_action('tainacan_register_exporters'); + + add_action( 'tainacan-enqueue-admin-scripts', array($this, 'enqueue_scripts') ); + } + + function enqueue_scripts() { + return null; + } + + public function register_exporter(array $exporter) { + + $defaults = [ //isso aqui vai mudar de acordo com a opção de exportação a se utilizada! + 'manual_mapping' => false, + 'manual_collection' => true + ]; + + $attrs = wp_parse_args($exporter, $defaults); + + if (!isset($attrs['slug']) || !isset($attrs['class_name']) || !isset($attrs['name'])) { + return false; + } + + $this->registered_exporters[$exporter['slug']] = $attrs; + + return true; + } + + function add_to_queue(\Tainacan\Exporter\Exporter $exporter_object) { + $data = $exporter_object->_to_Array(true); + $exporter = $this->get_exporter_by_object($exporter_object); + + $exporter_name = sprintf( __('%s Exporter', 'tainacan'), $exporter['name'] ); + + $bg_process = $this->bg_exporter->data($data)->set_name($exporter_name)->save(); + if ( is_wp_error($bg_process->dispatch()) ) { + return false; + } + return $bg_process; + } + + public function unregister_exporter($slug) { + unset($this->registered_exporters[$slug]); + } + + public function get_registered_exporters() { + return $this->registered_exporters; + } + + public function get_exporter($slug) { + $exporters = $this->get_registered_exporters(); + if (isset($exporters[$slug])) { + return $exporters[$slug]; + } + return null; + } + + function get_exporter_by_object(\Tainacan\exporter\Exporter $exporter_object) { + $class_name = get_class($exporter_object); + $class_name = '\\' . $class_name; + $exporters = $this->get_registered_exporters(); + foreach ($exporters as $exporter) { + if ($exporter['class_name'] == $class_name) + return $exporter; + } + return null; + } + + public function initialize_exporter($slug) { + $exporter = $this->get_exporter($slug); + if ( is_array($exporter) && isset($exporter['class_name']) && class_exists($exporter['class_name']) ) { + return new $exporter['class_name'](); + } + return false; + } +} + +global $Tainacan_Exporter_Handler; +$Tainacan_Exporter_Handler = new Export_Handler(); + ?> \ No newline at end of file diff --git a/src/exporter/class-tainacan-exporter.php b/src/exporter/class-tainacan-exporter.php new file mode 100644 index 000000000..870bfcaab --- /dev/null +++ b/src/exporter/class-tainacan-exporter.php @@ -0,0 +1,868 @@ + [ + * 'id' => 12, + * 'mapping' => [ + * 30 => 'column1' + * 31 => 'column2' + * ], + * 'total_items' => 1234, + * 'source_id' => 55 + * ], + * + * use add_collection() and remove_collection() to interact with thiis array. + * + * + * @var array + */ + protected $collections = []; + + /** + * Stores the options for the importer. Each importer might use this property to save + * their own specific option + * @var array + */ + private $options = []; + + /** + * Stores the default options for the importer options + * @var array + */ + protected $default_options = []; + + private $accepts = [ + 'file' => true, + 'url' => false, + ]; + + /** + * Declares what are the steps the importer will run, in the right order. + * + * By default, there is only one step, and the callback is the process_collections method + * that process items for the collections in the collections array. + * + * Child classes may declare as many steps as they want and can keep this default step to use + * this method for import the items. But it is optional. + * + * @var array + */ + protected $steps = [ + [ + 'name' => 'Import Items', + 'progress_label' => 'Importing Items', + 'callback' => 'process_collections' + ] + ]; + + /** + * Transients is used to store temporary data to be used accross multiple requests + * + * Add and remove transient data using add_transient() and delete_transient() methods + * + * Transitens can be strings, numbers or arrays. Avoid storing objects. + * + * @var array + */ + private $transients = []; + + private $current_step = 0; + + private $in_step_count = 0; + + private $current_collection = 0; + + private $current_collection_item = 0; + + private $url = ''; + + private $log = []; + + private $error_log = []; + + /** + * Wether to abort importer execution. + * @var bool + */ + private $abort = false; + + /** + * List of attributes that are saved in DB and that are used to + * reconstruct the object + * @var array + */ + private $array_attributes = [ + 'url', + 'current_collection_item', + 'current_collection', + 'in_step_count', + 'current_step', + 'transients', + 'options', + 'collections', + 'tmp_file' + ]; + + public function __construct($attributess = array()) { + if (!session_id()) { + @session_start(); + } + + $this->id = uniqid(); + $author = get_current_user_id(); + if($author) { + $this->add_transient('author', $author); + } + + $_SESSION['tainacan_exporter'][$this->get_id()] = $this; + if (!empty($attributess)) { + foreach ($attributess as $attr => $value) { + $method = 'set_' . $attr; + if (method_exists($this, $method)) { + $this->$method($value); + } + } + } + } + + public function _to_Array($short = false) { + $return = ['id' => $this->get_id()]; + foreach ($this->array_attributes as $attr) { + $method = 'get_' . $attr; + $return[$attr] = $this->$method(); + } + $return['class_name'] = get_class($this); + + global $Tainacan_Exporter_Handler; + $exporter_definition = $Tainacan_Exporter_Handler->get_exporter_by_object($this); + + if ($short === false) { + $return['manual_collection'] = $exporter_definition['manual_collection']; + $return['manual_mapping'] = $exporter_definition['manual_mapping']; + $return['accepts'] = $this->accepts; + $return['options_form'] = $this->options_form(); + } + + return $return; + } + + ///////////////////// + // Getters and setters + + /** + * @return string + */ + public function get_id(){ + return $this->id; + } + + /** + * Set URL + * @param $url string + * @return bool + */ + public function set_url($url) + { + if(!empty($url) && !is_array($url)) + { + $this->url = rtrim(trim($url), "/"); + return true; + } + + return false; + } + + /** + * @return string or bool + */ + public function get_url() + { + if(!empty($this->url)) + { + return $this->url; + } + + return false; + } + + public function get_current_step() { + return $this->current_step; + } + + public function set_current_step($value) { + $this->current_step = $value; + } + + public function get_in_step_count() { + return $this->in_step_count; + } + + public function set_in_step_count($value) { + $this->in_step_count = $value; + } + + public function get_current_collection() { + return $this->current_collection; + } + + public function set_current_collection($value) { + $this->current_collection = $value; + } + + public function get_current_collection_item() { + return $this->current_collection_item; + } + + public function set_current_collection_item($value) { + $this->current_collection_item = $value; + } + + public function get_tmp_file(){ + return $this->tmp_file; + } + + public function set_tmp_file($filepath){ + $this->tmp_file = $filepath; + } + + public function get_collections() { + return $this->collections; + } + + public function set_collections($value) { + $this->collections = $value; + } + + /** + * Gets the options for this importer, including default values for options + * that were not set yet. + * @return array Importer options + */ + public function get_options() { + return array_merge($this->default_options, $this->options); + } + + /** + * Set the options array + * @param array $options + */ + public function set_options($options) { + $this->options = $options; + } + + /** + * Set the default options values. + * + * Must be called from the __construct method of the child importer class to set default values. + * + * @param array $options + */ + protected function set_default_options($options) { + $this->default_options = $options; + } + + + public function set_steps($steps) { + $this->steps = $steps; + } + + public function get_steps() { + return $this->steps; + } + + + private function get_transients() { + return $this->transients; + } + + private function set_transients(array $data) { + $this->transients = $data; + } + + public function get_log() { + return $this->log; + } + + public function get_error_log() { + return $this->error_log; + } + + //////////////////////////////////// + // Utilities + + + /** + * @param $file File to be managed by importer + * @return bool + */ + public function add_file( $file ){ + $new_file = $this->upload_file( $file ); + if ( is_numeric( $new_file ) ) { + $this->tmp_file = get_attached_file( $new_file ); + return true; + } else { + return false; + } + } + + + /** + * log the actions from importer + * + * @param $type + * @param $messagelog + */ + public function add_log($message ){ + $this->log[] = $message; + } + public function add_error_log($message ){ + $this->error_log[] = $message; + } + + public function add_collection(array $collection) { + if (isset($collection['id'])) { + $this->remove_collection($collection['id']); + $this->collections[] = $collection; + } + } + + public function remove_collection($col_id) { + foreach ($this->get_collections() as $index => $col) { + if ($col['id'] == $col_id) { + unset($this->collections[$index]); + break; + } + } + } + + /** + * internal function to upload the file + * + * @param $path_file + * @return array $response + */ + private function upload_file( $file_array ){ + //$name = basename( $path_file ); + //$file_array['name'] = $name; + //$file_array['tmp_name'] = $path_file; + //$file_array['size'] = filesize( $path_file ); + + if ( !function_exists('media_handle_upload') ) { + require_once(ABSPATH . "wp-admin" . '/includes/image.php'); + require_once(ABSPATH . "wp-admin" . '/includes/file.php'); + require_once(ABSPATH . "wp-admin" . '/includes/media.php'); + } + //var_dump(media_handle_sideload( $file_array, 0 )); die; + return media_handle_sideload( $file_array, 0 ); + } + + /** + * get the content form url and creates a file + * + * @param $url + * @return array + */ + public function fetch_from_remote( $url ){ + $tmp = wp_remote_get( $url ); + if( !is_wp_error($tmp) && isset( $tmp['body'] ) ){ + $file = fopen( $this->get_id().'.txt', 'w' ); + fwrite( $file, $tmp['body'] ); + fclose( $file ); + return $this->add_file( $this->get_id().'.txt' ); + } + } + + /** + * Gets one option from the options array. + * + * Checks if option exist or if it have a default value. Otherwise return an empty string + * + * @param string $key the desired option + * @return mixed the option value, the default value or an empty string + */ + public function get_option($key) { + $options = $this->get_options(); + return isset($options[$key]) ? $options[$key] : ''; + } + + public function add_transient($key, $data) { + $this->transients[$key] = $data; + } + + public function delete_transient($key) { + if (isset($this->transients[$key])) + unset($this->transients[$key]); + } + + public function get_transient($key) { + if (isset($this->transients[$key])) + return $this->transients[$key]; + return null; + } + + public function is_finished() + { + if($this->current_step >= count($this->steps)) + { + return true; + } + + return false; + } + + /** + * Cancel Scheduled abortion at the end of run() + * @return void + */ + protected function cancel_abort() { + $this->abort = false; + } + + /** + * Schedule importer abortion at the end of run() + * @return void + */ + protected function abort() { + $this->abort = true; + } + + /** + * Return wether importer should abort execution or not + * @return bool + */ + public function get_abort() { + return $this->abort; + } + + /** + * Gets the current label to be displayed below the progress bar to give + * feedback to the user. + * + * It automatically gets the attribute progress_label from the current step running. + * + * Importers may change this label whenever they want + * + * @return string + */ + public function get_progress_label() { + $current_step = $this->get_current_step(); + $steps = $this->get_steps(); + $label = ''; + if ( isset($steps[$current_step]) ) { + $step = $steps[$current_step]; + if ( isset($step['progress_label']) ) { + $label = $step['progress_label']; + } elseif ( isset($step['name']) ) { + $label = $step['name']; + } + } + + if ( sizeof($steps) > 1 ) { + $preLabel = sprintf( __('Step %d of %d', 'tainacan'), $current_step + 1, sizeof($steps) ); + $label = $preLabel . ': ' . $label; + } + + if ( empty($label) ) { + $label = __('Running Importer', 'tainacan'); + } + + return $label; + + } + + /** + * Gets the current value to build the progress bar and give feedback to the user + * on the background process that is running the importer. + * + * It does so by comparing the "size" attribute with the $in_step_count class attribute + * where size indicates the total size of iterations the step will take and $this->in_step_count + * is the current iteration. + * + * For the step with "process_items" as a callback, this method will look for the the $this->collections array + * and sum the value of all "total_items" attributes of each collection. Then it will look for + * $this->get_current_collection and $this->set_current_collection_item to calculate the progress. + * + * The value must be from 0 to 100 + * + * If a negative value is passed, it is assumed that the progress is unknown + */ + public function get_progress_value() { + $current_step = $this->get_current_step(); + $steps = $this->get_steps(); + $value = -1; + + if ( isset($steps[$current_step]) ) { + $step = $steps[$current_step]; + + if ($step['callback'] == 'process_collections') { + + $totalItems = 0; + $currentItem = $this->get_current_collection_item(); + $current_collection = $this->get_current_collection(); + $collections = $this->get_collections(); + + foreach ($collections as $i => $col) { + if ( isset($col['total_items']) && is_integer($col['total_items']) ) { + $totalItems += $col['total_items']; + if ($i < $current_collection) { + $currentItem += $col['total_items']; + } + } + } + + if ($totalItems > 0) { + $value = round( ($currentItem/$totalItems) * 100 ); + } + + + } else { + + if ( isset($step['total']) && is_integer($step['total']) && $step['total'] > 0 ) { + $current = $this->get_in_step_count(); + $value = round( ($current/$step['total']) * 100 ); + } + + } + + + } + return $value; + } + + /** + * Sets the total attribute for the current step + * + * The "total" attribute of a step indicates the number of iterations this step will take to complete. + * + * The iteration is counted using $this->in_step_count attribute, and comparing the two values gives us + * the current progress of the process. + * + */ + protected function set_current_step_total($value) { + $this->set_step_total($this->get_current_step(), $value); + } + + /** + * Sets the total attribute for a given step + * + * The "total" attribute of a step indicates the number of iterations this step will take to complete. + * + * The iteration is counted using $this->in_step_count attribute, and comparing the two values gives us + * the current progress of the process. + * + */ + protected function set_step_total($step, $value) { + $steps = $this->get_steps(); + if (isset($steps[$step]) && is_array($steps[$step])) { + $steps[$step]['total'] = $value; + $this->set_steps($steps); + } + } + + + /////////////////////////////// + // Abstract methods + + + /** + * get the metadata of file/url to allow mapping + * should return an array + * + * Used when $manual_mapping is set to true, to build the mapping interface + * + * @return array $metadata_source the metadata from the source + */ + public function get_source_metadata() {} + + /** + * get values for a single item + * + * @param $index + * @return array with metadatum_source's as the index and values for the + * item + * + * Ex: [ 'Metadatum1' => 'value1', 'Metadatum2' => [ 'value2','value3' ] + */ + abstract public function process_item( $index, $collection_id ); + + + + /** + * Method implemented by the child importer class to return the total number of items that will be imported + * + * Used to build the progress bar + * + * @return int + */ + public function get_source_number_of_items() {} + + + /** + * Method implemented by child importer to return the HTML of the Options Form to be rendered in the Importer page + */ + public function options_form() {} + + + + //////////////////////////////////////// + // Core methods + + /** + * process an item from the collections queue + * + */ + public function process_collections() { + + $current_collection = $this->get_current_collection(); + $collections = $this->get_collections(); + $collection_definition = isset($collections[$current_collection]) ? $collections[$current_collection] : false; + $current_collection_item = $this->get_current_collection_item(); + + $this->add_log('Processing item ' . $current_collection_item); + $processed_item = $this->process_item( $current_collection_item, $collection_definition ); + if( $processed_item ) { + + if( is_bool($processed_item) ){ + return $this->next_item(); + } + + $this->add_log('Inserting item ' . $current_collection_item); + $this->insert( $processed_item, $current_collection ); + } else { + $this->add_error_log('failed on item '. $current_collection_item ); + } + + return $this->next_item(); + } + + protected function next_item() { + + $current_collection = $this->get_current_collection(); + $current_collection_item = $this->get_current_collection_item(); + $collections = $this->get_collections(); + $collection = $collections[$current_collection]; + + $current_collection_item ++; + $this->set_current_collection_item($current_collection_item); + + if ($current_collection_item >= $collection['total_items']) { + return $this->next_collection(); + } + + return $current_collection_item; + + } + + protected function next_collection() { + + $current_collection = $this->get_current_collection(); + $collections = $this->get_collections(); + + $this->set_current_collection_item(0); + + $current_collection ++; + + if (isset($collections[$current_collection])) { + $this->set_current_collection($current_collection); + return $current_collection; + } + + return false; + + + } + + protected function next_step() { + + $current_step = $this->get_current_step(); + $steps = $this->get_steps(); + + $current_step ++; + $this->set_current_step($current_step); + + if (isset($steps[$current_step])) { + return $current_step; + } + + return false; + + + } + + /** + * insert processed item from source to Tainacan + * + * @param array $processed_item Associative array with metadatum source's as index with + * its value or values + * @param integet $collection_index The index in the $this->collections array of the collection the item is beeing inserted into + * + * @return Tainacan\Entities\Item Item inserted + */ + public function insert( $processed_item, $collection_index ) { + + remove_action( 'post_updated', 'wp_save_post_revision' ); + $collections = $this->get_collections(); + $collection_definition = isset($collections[$collection_index]) ? $collections[$collection_index] : false; + if ( !$collection_definition || !is_array($collection_definition) || !isset($collection_definition['id']) || !isset($collection_definition['mapping']) ) { + $this->add_error_log('Collection misconfigured'); + return false; + } + + $collection = \Tainacan\Repositories\Collections::get_instance()->fetch($collection_definition['id']); + + $Tainacan_Metadata = \Tainacan\Repositories\Metadata::get_instance(); + $Tainacan_Item_Metadata = \Tainacan\Repositories\Item_Metadata::get_instance(); + $Tainacan_Items = \Tainacan\Repositories\Items::get_instance(); + + $Tainacan_Items->disable_logs(); + $Tainacan_Metadata->disable_logs(); + $Tainacan_Item_Metadata->disable_logs(); + + $item = new Entities\Item(); + $itemMetadataArray = []; + + if( is_array( $processed_item ) ){ + foreach ( $processed_item as $metadatum_source => $values ){ + $tainacan_metadatum_id = array_search( $metadatum_source, $collection_definition['mapping'] ); + $metadatum = $Tainacan_Metadata->fetch( $tainacan_metadatum_id ); + + if( $metadatum instanceof Entities\Metadatum ){ + $singleItemMetadata = new Entities\Item_Metadata_Entity( $item, $metadatum); // *empty item will be replaced by inserted in the next foreach + $singleItemMetadata->set_value( $values ); + $itemMetadataArray[] = $singleItemMetadata; + } else { + $this->add_error_log('Metadata ' . $metadatum_source . ' not found'); + } + + } + } + + if( !empty( $itemMetadataArray ) && $collection instanceof Entities\Collection ){ + $item->set_collection( $collection ); + + if( $item->validate() ){ + $insertedItem = $Tainacan_Items->insert( $item ); + } else { + $this->add_error_log( 'Error inserting item' ); + $this->add_error_log( $item->get_errors() ); + return false; + } + + foreach ( $itemMetadataArray as $itemMetadata ) { + $itemMetadata->set_item( $insertedItem ); // *I told you + + if( $itemMetadata->validate() ){ + $result = $Tainacan_Item_Metadata->insert( $itemMetadata ); + } else { + $this->add_error_log('Error saving value for ' . $itemMetadata->get_metadatum()->get_name()); + $this->add_error_log($itemMetadata->get_errors()); + continue; + } + + //if( $result ){ + // $values = ( is_array( $itemMetadata->get_value() ) ) ? implode( PHP_EOL, $itemMetadata->get_value() ) : $itemMetadata->get_value(); + // $this->add_log( 'Item ' . $insertedItem->get_id() . + // ' has inserted the values: ' . $values . ' on metadata: ' . $itemMetadata->get_metadatum()->get_name() ); + //} else { + // $this->add_error_log( 'Item ' . $insertedItem->get_id() . ' has an error' ); + //} + } + + $insertedItem->set_status('publish' ); + + if($insertedItem->validate()) { + $insertedItem = $Tainacan_Items->update( $insertedItem ); + + $this->after_inserted_item( $insertedItem, $collection_index ); + } else { + $this->add_error_log( 'Error publishing Item' ); + $this->add_error_log( $insertedItem->get_errors() ); + return false; + } + + return $insertedItem; + + } else { + $this->add_error_log( 'Collection not set'); + return false; + } + + } + + /** + * allow importers executes process after item is insertes + * @param array $insertedItem Associative array with inserted item + * @param integer $collection_index The index in the $this->collections array of the collection the item is beeing inserted into + * + */ + public function after_inserted_item($insertedItem, $collection_index){} + + /** + * runs one iteration + */ + public function run(){ + + if ($this->is_finished()) { + return false; + } + + $steps = $this->get_steps(); + $current_step = $this->get_current_step(); + $method_name = $steps[$current_step]['callback']; + + if (method_exists($this, $method_name)) { + $author = $this->get_transient('author'); + $this->add_log('User in process: ' . $author); + wp_set_current_user($author); + $result = $this->$method_name(); + } else { + $this->add_error_log( 'Callback not found for step ' . $steps[$current_step]['name']); + $result = false; + } + if($result === false || (!is_numeric($result) || $result < 0)) { + //Move on to the next step + $this->set_in_step_count(0); + $return = $this->next_step(); + } else if(is_numeric($result) && $result > 0) { + $this->set_in_step_count($result); + $return = $result; + } + + return $return; + + + } +} \ No newline at end of file