Jump to content
hellomoto

Trying to add add'l Fieldtype support to ImportPagesCSV (MapMarker & Repeater; Page ✓)

Recommended Posts

Resources (Ryan's own):

So I edit the accepted $fieldtypes on :76:

	/**
	 * List of Fieldtypes that we support importing to
	 *
	 */
	protected $fieldtypes = array(
		'FieldtypePageTitle',
		'FieldtypeText',
		'FieldtypeTextarea',
		'FieldtypeInteger',
		'FieldtypeFloat',
		'FieldtypeEmail',
		'FieldtypeURL',
		'FieldtypeCheckbox',
		'FieldtypeFile',
		'FieldtypePage',
		'FieldtypeMapMarker',
		'FieldtypePassword',
		'FieldtypeRepeater'
		);

Page, MapMarker, Password and Repeater were added by me. 

Then alter importPageValue:

	/**
	 * Assign a value to a page field 
	 *
	 */
	protected function importPageValue(Page $page, $name, $value) {

		$field = $this->fields->get($name); 

		if($field->type instanceof FieldtypeFile) {

			$value = trim($value); 
			// split delimeted data to an array
			$value = preg_split('/[\r\n\t|]+/', $value); 
			if($field->maxFiles == 1) $value = array_shift($value);
			$data = $page->ImportPagesCSVData; 
			$data[$name] = $value; 
			$page->ImportPagesCSVData = $data; 

		} elseif($field->type instanceof FieldtypePage) {

			$value = trim($value);
			if(wire("pages")->find("$name=$value")) $page->set($name, $value);

		} elseif($field->type instanceof FieldtypeMapMarker) {

			$value = trim($value);
			$page->set($name->address, $value);

		} elseif($field->type instanceof FieldtypeRepeater) {

			//

		} else { 

			$page->set($name, $value); 
			if($name == 'title') $page->name = $this->sanitizer->pageName($value, 2); // Sanitizer::translate
			elseif($name == 'fullname') {
				$page->name = $this->sanitizer->pageName($value, true);
			} 
		}
	}

Page import works with ID values, which was trivial to incorporate; passwords too. MapMarker and Repeater as you might guess do not. 

How can I save the map->address value? Hopefully it will update the corresponding map fields too but one thing at a time.

As for the repeaters... LostKobrakai tipped me off to 

foreach($page->engines as $e) {
    foreach($e->fields as $field) {
        echo $field; echo $e->get($field);
    }
}

which works for their names and values, but in this function you're passed the field, and something like


foreach($page->$field as $e) {
    foreach($e->fields as $field) {
    	echo $field;
        echo $e->get($field);
    }
}

doesn't work... and what it would need to do inside anyway is check for a subfield whose name is equal to the column header (choose the repeater field itself e.g., engines in the select per repeater subfield value, e.g., engine_fueltype), then explode that cell value by pipes ('|'), and for each subvalue, populate the repeater#->subvalue... but before all that I need to be able to iterate through the subfields from the field in this function. 

Anyone have any ideas?

Share this post


Link to post
Share on other sites

Or the repeater subfields could be added to the select list for the field connections directly... that would simplify the importPageValue part.

But still... if I do 

	/**
	 * Build the "Step 2" form to connect the fields
	 *
	 */
	protected function buildForm2() {

		$form = $this->modules->get("InputfieldForm"); 
		$form->method = 'post';		
		$form->action = './'; 
		$form->description = "Step 2: Connect the fields"; 
		$form->value = 
			"Below is a list of fields found in your CSV file. " . 
			"For each of them, select the field it should import to. " . 
			"Leave any fields you want to exclude blank. " . 
			"Once finished, click 'Start Import' at the bottom of this page. " . 
			"Note: any field names in your CSV file that match those in your site " . 
			"will be automatically selected.";

		$fp = fopen($this->csvFilename, "r"); 
		$data = fgetcsv($fp, 0, $this->session->csvDelimeter, $this->session->csvEnclosure);

		foreach($data as $key => $value) {

			$f = $this->modules->get("InputfieldSelect"); 
			$f->name = "csv" . $key;
			$f->label = $value; 
			$f->addOption(''); 

			foreach($this->template->fieldgroup as $field) {
				$valid = false;

				foreach($this->fieldtypes as $ft) {
					if($field->type instanceof $ft) {
						$valid = true;
						break;
					}
				}

				if(!$valid) continue; 

				if($field->type instanceof FieldtypeRepeater) {
					echo count($field->repeaterFields);
					foreach($field->repeaterFields as $item) {
						echo wire("fields")->get($item)->name . '<br/>';
						//$label = wire("fields")->get($item);
						//$label = $item->name;
						//$f->addOption($item->name);
					}
				}

				$label = $field->name; 
				$f->addOption($field->name, $label); 
				if($field->name == $value) $f->attr('value', $field->name); 
			}

			$form->add($f);
		}

		fclose($fp); 

		$this->addSubmit($form, 'Start Import');

		return $form;
	}

That returns 8 for count($field->repeaterFields), which is correct. Yet, executing the next foreach statement echoes 23 iterations of name of each subfield. For example: repeater field = "engines", its repeaterFields = ["engine_field1", "engine_field2", "engine_field3", "engine_field4", "engine_field5", "engine_field6", "engine_field7", "engine_field8"] and each of those are echoed out together -- 23x. Should just be once, for a total of 8 lines, not 184...

Share this post


Link to post
Share on other sites

The repeater field value should be a pageArray, with all the repeater pages. Problem with repeaters, you're only coping fields right now. But repeaters are own hidden pages, which have to be generated before you can copy over the fields. Have a look here how to handle repeaters from the api side: http://processwire.com/api/fieldtypes/repeaters/

} elseif($field->type instanceof FieldtypeRepeater) {
  foreach($value as $e){
    …
  }
}

For the map marker: Did you take a look what's being returned by $value? I doubt it's a string so $value = trim($value); doesn't do a thing. Try this, as $value should carry address as part of it, after a quick look at the source of the module.

$page->set($name, $value);

Share this post


Link to post
Share on other sites

I know repeaters are pages... For my main template that I am trying to get this to work for primarily, I have a repeater field engines with the default ready set to 2. So I would assume that these are created nonetheless when creating a page of that template via the API... This is tricky. I think perhaps the individual repeater fields should be included in the fields list when matching them prior to actually importing and then separate the values into repeaters on import? 

Anyway I think I'll try to get the mapmarker working first. The $value in importPageValue isn't what's returned by MapMarker, it's the value you're setting for the field, from the CSV.

Share this post


Link to post
Share on other sites

Teppo got me to realize that I can simply do $page->$field->address = $value. So now map marker address can be imported :) which means it's time to tackle the repeaters... oy.

  • Like 1

Share this post


Link to post
Share on other sites

Actually I don't think repeaters really can be set in importPageValue, because the column header isn't passed to it. So it would be better to set them as options in build form 2 (and more direct).

Share this post


Link to post
Share on other sites

Great mod. But importing Map Marker didn't work for me. Could anyone help please?

Also posted this here:

https://processwire.com/talk/topic/383-module-import-pages-from-csv-file/page-9

I see that you aren't following either topic so not sure if you will see these responses, but I posted a detailed response about how you can use Batch Child Editor for importing CSV files into pages with MapMarker fields in response to your post in the other thread. Hope that helps.

Share this post


Link to post
Share on other sites

Reading docs may really help sometimes...

Quote from the @hellomoto's code:

elseif($field->type instanceof FieldtypeMapMarker) {
 
$value = trim($value);
$page->set($name->address, $value);
 
}

And now some code from Map Marker docs ( https://processwire.com/talk/topic/9711-map-marker-map/ ):

$page->marker->address = 'Disney Land';

Now it's obvious that Hellomoto's code should be like this:

elseif($field->type instanceof FieldtypeMapMarker) {
 
$value = trim($value);
$page->$name->address = $value;
 
}

Tried this, works like a charm. Lat and Lng subfields are generated automagically if the address is more or less correct.

Thanks @Hellomoto for a great mod! @Ryan, maybe it's time to add this to Your original module?

Share this post


Link to post
Share on other sites

code should be:

 

elseif($field->type instanceof FieldtypeMapMarker) {
 
			$value = trim($value);
			//$page->set($name->address, $value);
 			 $page->$field->$name = $value;
		} 

 

Share this post


Link to post
Share on other sites
On 7/23/2015 at 4:26 PM, theoretic said:

Reading docs may really help sometimes...

Quote from the @hellomoto's code:


elseif($field->type instanceof FieldtypeMapMarker) {
 
$value = trim($value);
$page->set($name->address, $value);
 
}

And now some code from Map Marker docs ( https://processwire.com/talk/topic/9711-map-marker-map/ ):


$page->marker->address = 'Disney Land';

Now it's obvious that Hellomoto's code should be like this:


elseif($field->type instanceof FieldtypeMapMarker) {
 
$value = trim($value);
$page->$name->address = $value;
 
}

Tried this, works like a charm. Lat and Lng subfields are generated automagically if the address is more or less correct.

Thanks @Hellomoto for a great mod! @Ryan, maybe it's time to add this to Your original module?

This is true, and is what I have, but it's not working in another custom import script I'm working on currently:

function importCSV($filepath, $template, $parent_id = null, $grandparent_id = null) {
  $csv = array_map('str_getcsv', file($filepath));
  array_walk($csv, function(&$a) use ($csv) {
    $a = array_combine($csv[0], $a); # set header keys
  });
  array_shift($csv); # remove column header
  //echo '<pre>'; print_r($csv); echo '</pre>';
  foreach($csv as $r) {
    $p = new Page();
    $p->name = wire('sanitizer')->pageName($r['title']);
    $p->template = $template;
    if($parent_id !== 0||null) {
      $p->parent_id = $parent_id;
    } elseif($parent_id == 0||null) {
      //echo $r['parent'].' ';
      $parent = wire('sanitizer')->pageName($r['parent']);
      $parent = str_replace('---','-',$parent);
      //echo $parent.' ';//echo $grandparent_id.'gp ';
      $parent = wire('pages')->get('title=' . $r['parent'] . ', parent_id=' . $grandparent_id)->id;
      //echo $parent.'p ';
      $p->parent_id = $parent;
      unset($r['parent']);
    }
    $p->save();
    $p->of(false);
    foreach($r as $k=>$v) {
      $v = trim($v);
      $fieldtype = wire('fields')->get('name='.$k)->type;
      if($fieldtype = 'FieldtypeImage') {
        $imgs = explode('|', $v);
        foreach($imgs as $i) {
          $p->$k = $i;
        }
      }
      elseif($fieldtype = 'FieldtypeMapMarker') {
        echo 'mapmarker';
        $p->set($k->address, $v); // $p->$k->address = $v;
      }
      else $p->$k = $v;
    }
    $p->save(); //echo '<br>';
  }
}

WHY not?? I have tried the way you mention here that's commented out in my code as well, to no avail. Why is this so difficult?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By MoritzLost
      This module allows you to integrate hCaptcha bot / spam protection into ProcessWire forms. hCaptcha is a great alternative to Google ReCaptcha, especially if you are in the EU and need to comply with privacy regulations.

      The development of this module is sponsored by schwarzdesign.
      The module is built as an Inputfield, allowing you to integrate it into any ProcessWire form you want. It's primarily intended for frontend forms and can be added to Form Builder forms for automatic spam protection. There's a step-by-step guide for adding the hCaptcha widget to Form Builder forms in the README, as well as instructions for API usage.
      Features
      Inputfield that displays an hCaptcha widget in ProcessWire forms. The inputfield verifies the hCaptcha response upon submission, and adds a field error if it is invalid. All hCaptcha configuration options for the widget (theme, display size etc) can be changed through the inputfield configuration, as well as programmatically. hCaptcha script options can be changed through a hook. Error messages can be translated through ProcessWire's site translations. hCaptcha secret keys and site-keys can be set for each individual inputfield or globally in your config.php. Error codes and failures are logged to help you find configuration errors. Please check the README for setup instructions.
      Links
      Github Repository and documentation InputfieldHCaptcha in the module directory (pending approval) Screenshots (configuration)

      Screenshots (hCaptcha widget)

       
       

       
    • By joshua
      This module is (yet another) way for implementing a cookie management solution.
      Of course there are several other possibilities:
      - https://processwire.com/talk/topic/22920-klaro-cookie-consent-manager/
      - https://github.com/webmanufaktur/CookieManagementBanner
      - https://github.com/johannesdachsel/cookiemonster
      - https://www.oiljs.org/
      - ... and so on ...
      In this module you can configure which kind of cookie categories you want to manage:

      You can also enable the support for respecting the Do-Not-Track (DNT) header to don't annoy users, who already decided for all their browsing experience.
      Currently there are four possible cookie groups:
      - Necessary (always enabled)
      - Statistics
      - Marketing
      - External Media
      All groups can be renamed, so feel free to use other cookie group names. I just haven't found a way to implement a "repeater like" field as configurable module field ...
      When you want to load specific scripts ( like Google Analytics, Google Maps, ...) only after the user's content to this specific category of cookies, just use the following script syntax:
      <script type="text/plain" data-type="text/javascript" data-category="statistics" data-src="/path/to/your/statistic/script.js"></script> <script type="text/plain" data-type="text/javascript" data-category="marketing" data-src="/path/to/your/mareketing/script.js"></script> <script type="text/plain" data-type="text/javascript" data-category="external_media" data-src="/path/to/your/external-media/script.js"></script> <script type="text/plain" data-type="text/javascript" data-category="marketing">console.log("Inline scripts are also working!");</script> The type has to be "optin" to get recognized by PrivacyWire, the data-attributes are giving hints, how the script shall be loaded, if the data-category is within the cookie consents of the user. These scripts are loaded asynchronously after the user made the decision.
      If you want to give the users the possibility to change their consent, you can use the following Textformatter:
      [[privacywire-choose-cookies]] It's planned to add also other Textformatters to opt-out of specific cookie groups or delete the whole consent cookie.
      You can also add a custom link to output the banner again with a link / button with following class:
      <a href="#" class="privacywire-show-options">Show Cookie Options</a> <button class="privacywire-show-options">Show Cookie Options</button> This module is still in development, but we already use it on several production websites.
      You find it here: PrivacyWire Git Repo
      Download as .zip
      I would love to hear your feedback 🙂
      CHANGELOG
      0.1.1 Debugging: fixed error during uninstall 0.1.0 Added new detection of async scripts for W3C Validation 0.0.6 CSS-Debugging for hiding unused buttons, added ProCache support for the JavaScript tag 0.0.5 Multi-language support included completely (also in TextFormatter). Added possibility to async load other assets (e.g. <img type="optin" data-category="marketing" data-src="https://via.placeholder.com/300x300">) 0.0.4 Added possibility to add an imprint link to the banner 0.0.3 Multi-language support for module config (still in development) 0.0.2 First release 0.0.1 Early development
    • By ICF Church
      Hi 👋
      Anyone else having this problem?
      Requirements:
      - Repeater (matrix & normal) with mutlilanguage fields (text, textarea…) 
      - Backend language set to something other than default (ie. German) 
      Reproduce:
      - Add a new repeater Item (ajax, I found no way to possible to disable it with matrix)

      (Notice how the default language tab is active instead of the backend language…)
      - Write something into the (default language) field
      - Try to save, if field is required, this will not work. If not required, then when reloading, the content will be inside the backend language field, instead of the default language field who was (presumably) active
      Analysis:
      When  loading  a new repeater element with ajax, the default langue tab is active, but the backend language inputfield is visible (with no visual indication). When writing into the field, it will populate the backend language. When manually clicking on the default language tab (which is already active), the field will switch to the actual default language field (which is [now] empty) (that can now be populated…)
      Also Notice, the labels of the elements to be added are in default language as well instead of the translated label (images instead of Bilder)…
      ProcessWire 3.0.148, Profields 0.0.5…
      Is it my system configuration, or does anyone else have the same issue? This is a screen recording of the problem:
      Issue: https://github.com/processwire/processwire-issues/issues/1179

      Screen Recording 2020-02-25 at 14.18.31.mov
    • By bernhard
      --- Please use RockFinder3 ---
    • By MoritzLost
      Cacheable Placeholders
      This module allows you to have pieces of dynamic content inside cached output. This aims to solve the common problem of having a mostly cacheable site, but with pieces of dynamic output here and there.  Consider this simple example, where you want to output a custom greeting to the current user:
      <h1>Good morning, <?= ucfirst($user->name) ?></h1> This snippet means you can't use the template cache (at least for logged-in users), because each user has a different name. Even if 99% of your output is static, you can only cache the pieces that you know won't include this personal greeting. A more common example would be CSRF tokens for HTML forms - those need to be unique by definition, so you can't cache the form wholesale.
      This module solves this problem by introducing cacheable placeholders - small placeholder tokens that get replaced during every request. The replacement is done inside a Page::render hook so it runs during every request, even if the response is served from the template cache. So you can use something like this:
      <h1>Good morning, {{{greeting}}}</h1> Replacement tokens are defined with a callback function that produces the appropriate output and added to the module through a simple hook:
      // site/ready.php wire()->addHookAfter('CachePlaceholders::getTokens', function (HookEvent $e) { $tokens = $e->return; $tokens['greeting'] = [ 'callback' => function (array $tokenData) { return ucfirst(wire('user')->name); } ]; $e->return = $tokens; }); Tokens can also include parameters that are parsed and passed to the callback function. There are more fully annotated examples and step-by-step instructions in the README on Github!
      Features
      A simple and fast token parser that calls the appropriate callback and runs automatically. Tokens may include multiple named or positional parameters, as well as multi-value parameters. A manual mode that allows you to replace tokens in custom pieces of cached content (useful if you're using the $cache API). Some built-in tokens for common use-cases: CSRF-Tokens, replacing values from superglobals and producing random hexadecimal strings. The token format is completely customizable, all delimiters can be changed to avoid collisions with existing tag parsers or template languages. Links
      Github Repository & documentation Module directory (pending approval) If you are interested in learning more, the README is very extensive, with more usage examples, code samples and usage instructions!
×
×
  • Create New...