Jump to content

Custom multi-column Fieldtype + Inputfield - extending for multi language usage?


Gadgetto
 Share

Recommended Posts

Hi,

for my GroupMailer module I've created a custom Fieldtype + Inputfield module which provides multi-column field values. The first field column is a visible text field and there are some other columns which are not presented to user (they are rendered as hidden form fields).

262017940_Bildschirmfoto2019-02-20um19_42_04.png.0d61e10a7761a26218c16bc01c60aea4.png

This is the database schema:

$schema['data'] = 'text NOT NULL'; // we're using 'data' to represent our 'subject' field
$schema['sendstatus'] = 'tinyint NOT NULL DEFAULT 0'; // message send status
$schema['recipients'] = "int(10) unsigned NOT NULL DEFAULT 0";  // recipients counter
$schema['sent'] = "int(10) unsigned NOT NULL DEFAULT 0";  // sent counter
$schema['started'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending start
$schema['finished'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending finished

This are the ___wakeupValue and ___sleepValue methods:

Spoiler

    /**
     * Convert from DB storage to API value.
     *
     * @param Page $page
     * @param Field $field
     * @param string|int|array $value
     * @return string|int|array|object $messagemeta
     *
     */
    public function ___wakeupValue(Page $page, Field $field, $value) {

        // if for some reason we already get a valid value, then just return it
        if ($value instanceof MessageMeta) return $value; 

        // start a blank value to be populated
        $messagemeta = $this->getBlankValue($page, $field); 

        // if we were given a blank value, then we've got nothing to do: just return a blank MessageMeta
        if (empty($value) || !is_array($value)) return $messagemeta; 

        // create new MessageMeta object
        $messagemeta = new MessageMeta();
        $messagemeta->subject = $value['data']; // @note: we're converting 'data' to 'subject'
        $messagemeta->sendstatus = $value['sendstatus'];
        $messagemeta->recipients = $value['recipients'];
        $messagemeta->sent = $value['sent'];
        $messagemeta->started = $value['started'];
        $messagemeta->finished = $value['finished']; 

        return $messagemeta;  
    }

    /**
     * Convert from API to DB storage value. 
     *              
     * @param Page $page
     * @param Field $field
     * @param string|int|array|object $value
     * @return array
     *
     */
    public function ___sleepValue(Page $page, Field $field, $value) {
        $messagemeta = $value;
        $sleepValue = array();
        // if we are given something other than an MessageMeta, 
        // then just return a blank array
        if (!$messagemeta instanceof MessageMeta) return $sleepValue; 

        // set MessageMeta to sleepValue
        $sleepValue = array(
            'data' => $messagemeta->subject,  // @note: subject is becoming data
            'sendstatus' => (int) $messagemeta->sendstatus,
            'recipients' => (int) $messagemeta->recipients, 
            'sent' => (int) $messagemeta->sent, 
            'started' => (int) $messagemeta->started, 
            'finished' => (int) $messagemeta->finished, 
        );

        return $sleepValue;
    }

 

Now I try to extend this Fieldtype/Inputfield to provide multi language features.

Only the first value ("data" which represents the "subject" field) should be/needs to be multi language!

I had a look at the built in Fieldtypes (e.g FieldtypeText & FieldtypeTextLanguage) which provides multi language support but I couldn't find a similar case (multi-value field with language support). All built in Fieldtypes are single-value fields.

I know this is a very "general" question but maybe somebody could push me in the right direction?

Link to comment
Share on other sites

I' really stuck here guys... ? Documentation is no help. Comparing with other multi-language fields doesn't help because they are all single-value fields.

Here are the full sources of my multi-value input field:

FieldtypeMessageMeta.module.php

Spoiler

<?php namespace ProcessWire;

/**
 * Fieldtype that stores MessageMeta for GroupMailer Messages
 *
 */

class FieldtypeMessageMeta extends Fieldtype {

    public static function getModuleInfo() {
        return array(
            'title' => 'GroupMailer MessageMeta',
            'version' => 1,
            'summary' => 'Fieldtype that stores meta data for GroupMailer Messages.',
            'installs' => 'InputfieldMessageMeta',
            //'requires' => 'ProcessGroupMailer', // needed to be uninstalled with ProcessGroupMailer!
        );
    }

    /**
     * Initialize this Fieldtype
     *
     */
    public function init() {
        parent::init();
        require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'MessageMeta.php'; 
    }

    /**
     * Return the required Inputfield used to populate a field of this type
     * 
     * @param Page $page
     * @param Field $field
     * @return null|_Module|\Inputfield|\Module
     * @throws WirePermissionException
     * 
     */
    public function getInputfield(Page $page, Field $field) {
        /** @var InputfieldMessageMeta $inputfield */
        $inputfield = $this->modules->get('InputfieldMessageMeta'); 

        // our inputfield requires a Page and Field
        $inputfield->setPage($page);
        $inputfield->setField($field); 

        return $inputfield; 
    }

    /**
     * Return all Fieldtypes derived from FieldtypeMessageMeta, which we will consider compatible
     *
     */
     public function ___getCompatibleFieldtypes(Field $field) {
        $fieldtypes = $this->wire(new Fieldtypes());
        foreach ($this->wire('fieldtypes') as $fieldtype) {
            if ($fieldtype instanceof FieldtypeMessageMeta) {
                $fieldtypes->add($fieldtype);
            }
        }
        return $fieldtypes; 
    }

    /**
     * Return a blank ready-to-populate version of a field of this type
     *
     */
    public function getBlankValue(Page $page, Field $field) {
        $messagemeta = new MessageMeta($page);
        $messagemeta->setTrackChanges(true); 
        return $messagemeta; 
    }


    /**
     * Convert from DB storage to API value.
     *
     * @param Page $page
     * @param Field $field
     * @param string|int|array $value
     * @return string|int|array|object $messagemeta
     *
     */
    public function ___wakeupValue(Page $page, Field $field, $value) {

        // if for some reason we already get a valid value, then just return it
        if ($value instanceof MessageMeta) return $value; 

        // generate a blank value to be populated
        $messagemeta = $this->getBlankValue($page, $field); 

        // if we were given a blank value, then we've got nothing to do: just return a blank MessageMeta
        if (empty($value) || !is_array($value)) return $messagemeta; 

        // create new MessageMeta object
        $messagemeta = new MessageMeta();
        $messagemeta->subject = $value['data']; // @note: we're converting 'data' to 'subject'
        $messagemeta->sendstatus = $value['sendstatus'];
        $messagemeta->recipients = $value['recipients'];
        $messagemeta->sent = $value['sent'];
        $messagemeta->started = $value['started'];
        $messagemeta->finished = $value['finished']; 

        return $messagemeta;  
    }

    /**
     * Convert from API to DB storage value. 
     *              
     * @param Page $page
     * @param Field $field
     * @param string|int|array|object $value
     * @return array
     *
     */
    public function ___sleepValue(Page $page, Field $field, $value) {
        $messagemeta = $value;
        $sleepValue = array();
        // if we are given something other than an MessageMeta, 
        // then just return a blank array
        if (!$messagemeta instanceof MessageMeta) return $sleepValue; 

        // set MessageMeta to sleepValue
        $sleepValue = array(
            'data' => $messagemeta->subject,  // @note: subject is becoming data
            'sendstatus' => (int) $messagemeta->sendstatus,
            'recipients' => (int) $messagemeta->recipients, 
            'sent' => (int) $messagemeta->sent, 
            'started' => (int) $messagemeta->started, 
            'finished' => (int) $messagemeta->finished, 
        );

        return $sleepValue;
    }

    /**
     * Given a value, make it clean for storage within a Page
     *
     * @param Page $page
     * @param Field $field
     * @param int|object|WireArray|string $value
     * @return int|null|object|MessageMeta|WireArray|string
     * @throws WireException
     * 
     */
    public function sanitizeValue(Page $page, Field $field, $value) {

        // if given a blank value, return a valid blank value
        if (empty($value)) return $this->getBlankValue($page, $field, $value); 

        // if given something other than an MessageMeta, throw an error
        if (!$value instanceof MessageMeta) {
            throw new WireException("Value set to field '$field->name' must be MessageMeta data"); 
        }

        // note that sanitization of individual fields within a given MessageMeta is already 
        // performed by the MessageMeta::set() method, so we don't need to do anything else here.

        return $value; 	
    }

    /**
     * Format a value for output, called when a Page's outputFormatting is on
     *
     */
    public function formatValue(Page $page, Field $field, $value) {
        // we actually don't need to do anything in here since each MessageMeta object
        // is doing this work in the MessageMeta::get() method.
        return $value; 
    }

    /**
     * Return the database schema that defines a MessageMeta object
     *
     */
    public function getDatabaseSchema(Field $field) {
        $schema = parent::getDatabaseSchema($field); 

        $schema['data'] = 'text NOT NULL'; // we're using 'data' to represent our 'subject' field
        $schema['sendstatus'] = 'tinyint NOT NULL DEFAULT 0'; // message send status
        $schema['recipients'] = "int(10) unsigned NOT NULL DEFAULT 0";  // recipients counter
        $schema['sent'] = "int(10) unsigned NOT NULL DEFAULT 0";  // sent counter
        $schema['started'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending start
        $schema['finished'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending finished

        // indexes, for any fields that need to be searchable from selectors
        $schema['keys']['data'] = 'FULLTEXT KEY `data` (`data`)';
        $schema['keys']['sendstatus'] = 'KEY sendstatus (sendstatus)';
        $schema['keys']['recipients'] = "KEY recipients (recipients)";
        $schema['keys']['sent'] = "KEY sent (sent)";
        $schema['keys']['started'] = "KEY started (started)";
        $schema['keys']['finished'] = "KEY finished (finished)";

        return $schema; 
    }

    /**
     * Method called when the field is database-queried from a selector 
     *
     */
    public function getMatchQuery($query, $table, $subfield, $operator, $value) {

        // If searching 'subject' then assume our default (data) field 
        if ($subfield == 'subject') $subfield = 'data';

        // if value is a formatted date, convert it to unix timestamp
        if (!ctype_digit("$value")) $value = strtotime($value);

        return parent::getMatchQuery($query, $table, $subfield, $operator, $value); 
    }

}

 

FieldtypeMessageMetaLanguage.module.php

Spoiler

<?php namespace ProcessWire;

require_once wire('config')->paths->modules . 'LanguageSupport/FieldtypeLanguageInterface.php';

/**
 * Multi-language capable MessageMeta field
 *
 */

class FieldtypeMessageMetaLanguage extends FieldtypeMessageMeta implements FieldtypeLanguageInterface {

    public static function getModuleInfo() {
        return array(
            'title' => 'GroupMailer MessageMeta (Multi-language)',
            'version' => 1,
            'summary' => 'Fieldtype that stores meta data for GroupMailer Messages in multiple languages.',
            'requires' => 'LanguageSupportFields',
            //'requires' => 'ProcessGroupMailer', // needed to be uninstalled with ProcessGroupMailer!
        );
    }

    /**
     * Sanitize value for storage
     * 
     * @param Page $page
     * @param Field $field
     * @param LanguagesValueInterface|string $value
     * @return LanguagesPageFieldValue
     *
     */
    public function sanitizeValue(Page $page, Field $field, $value) {
        if (is_object($value) && $value instanceof LanguagesPageFieldValue) {
            // great, already what we wanted
        } else {
            // convert it to a LanguagesPageFieldValue
            $pageValue = $page->data($field->name); // raw unformatted value, with no load possible
            if (!$pageValue instanceof LanguagesPageFieldValue) {
                $pageValue = new LanguagesPageFieldValue($page, $field, $pageValue); // #98
            }
            if (is_array($value)) {
                $pageValue->importArray($value); 
            } else {
                $user = $this->wire('user');
                $language = $user ? $user->language : null;
                if ($language) $pageValue->setLanguageValue($language->id, (string) $value); 
            }
            $value = $pageValue; 
        }
        return $value; 
    }

    /**
     * Return the database schema in specified format
     * 
     * @param Field $field
     * @return array
     *
     */
    public function getDatabaseSchema(Field $field) {
        $schema = parent::getDatabaseSchema($field);
        
        $languageSupport = $this->wire('modules')->get('LanguageSupport'); 
        $maxIndex = (int) $this->wire('database')->getMaxIndexLength();
    
        // note that we use otherLanguagePageIDs rather than wire('languages') because
        // it's possible that this method may be called before the languages are known 
        foreach ($languageSupport->otherLanguagePageIDs as $languageID) {
            // $schema['data' . $languageID] = $schema['data'];
            $schema['data' . $languageID] = 'text';
            $schema['keys']["data_exact{$languageID}"] = "KEY `data_exact{$languageID}` (`data{$languageID}`($maxIndex))";
            $schema['keys']["data{$languageID}"] = "FULLTEXT KEY `data{$languageID}` (`data{$languageID}`)";
        }
    
        return $schema;
    }

    /**
     * Format value for output, basically typecasting to a string and sending to textformatters from FieldtypeText
     * 
     * @param Page $page
     * @param Field $field
     * @param LanguagesValueInterface|string $value
     * @return string
     *
     */
    public function formatValue(Page $page, Field $field, $value) {
        return parent::formatValue($page, $field, (string) $value); 
    }

    /**
     * Given a value, return an portable version of it as array
     *
     * @param Page $page
     * @param Field $field
     * @param string|int|float|array|object|null $value
     * @param array $options Optional settings to shape the exported value, if needed.
     * @return string|float|int|array
     *
     */
    public function ___exportValue(Page $page, Field $field, $value, array $options = array()) {
        if (isset($options['sleepValue'])) {
            // allow a sleepValue option, for use by other language Fieldtypes that delegate
            // their exportValue to this one, like FieldtypeTextareaLanguage
            $sleepValue = $options['sleepValue'];
        } else {
            $sleepValue = $this->sleepValue($page, $field, $value);
        }
        $exportValue = array();
        foreach ($sleepValue as $k => $v) {
            if ($k === 'data') {
                $exportValue['default'] = $v;
            } elseif (strpos($k, 'data') === 0) {
                $languageID = substr($k, 4);
                $language = $this->wire('languages')->get((int) $languageID);
                $exportValue[$language->name] = $v;
            } else {
                $exportValue[$k] = $v;
            }
        }
        return $exportValue;
    }
    
    public function ___importValue(Page $page, Field $field, $value, array $options = array()) {
        if (is_null($value)) $value = '';
        if (is_string($value)) {
            $v = $value;
            $value = $field->type->exportValue($page, $field, $page->getUnformatted($field->name), $options);
            $value['default'] = $v; 
        }
        if (!is_array($value)) {
            throw new WireException('Array value expected for multi-language importValue');
        }
        /** @var Languages $languages */
        $languages = $this->wire('languages');
        /** @var LanguagesPageFieldValue $importValue */
        $importValue = $page->get($field->name); 
        foreach ($value as $languageName => $languageValue) {
            $language = $languages->get($languageName); 
            if (!$language->id) continue; 
            $importValue->setLanguageValue($language->id, $languageValue); 
        }
        return $importValue;
    }

}

 

MessageMeta.php

Spoiler

<?php namespace ProcessWire;

/**
 * An individual MessageMeta object for a Page
 *
 */
class MessageMeta extends WireData {

	/** @var string $dateFormat Holds the date format from wire config */
	protected $dateFormat;

	/**
	 * We keep a copy of the $page that owns this MessageMeta so that we can follow
	 * its outputFormatting state and change our output per that state
	 *
	 */
	protected $page; 

	/**
	 * Construct a new MessageMeta object
	 *
	 */
	public function __construct() {
		$this->dateFormat = $this->wire('config')->dateFormat;

		// define the fields that represent our MessageMeta object (and their default/blank values)
        $this->set('subject', ''); 
        $this->set('sendstatus', 0); 
        $this->set('recipients', 0);
        $this->set('sent', 0);
        $this->set('started', 0); 
		$this->set('finished', 0);
	}

	/**
	 * Set a value to the MessageMeta object
	 *
	 */
	public function set($key, $value) {
		if ($key == 'page') {
			$this->page = $value;
			return $this;

		} elseif ($key == 'subject') {
			// regular text sanitizer
			$value = $this->sanitizer->text($value);

		} elseif ($key == 'started' || $key == 'finished') {
			// convert date string to unix timestamp
			if ($value && !ctype_digit("$value")) $value = strtotime($value); 	

			// sanitized date value is always an integer
			$value = (int) $value;
		}

		return parent::set($key, $value); 
	}

	/**
	 * Retrieve a value from the MessageMeta object
	 *
	 */
	public function get($key) {
		$value = parent::get($key); 

		// if the page's output formatting is on, then we'll return formatted values
		if ($this->page && $this->page->of()) {

			if ($key == 'subject') {
				// return entity encoded versions of string
				$value = $this->sanitizer->entities($value); 
			} elseif ($key == 'started' || $key == 'finished') {
				// format a unix timestamp to a date string
				$value = date($this->dateFormat, $value); 				
			}

		}
		
		return $value; 
	}

	/**
	 * Provide a default rendering for a MessageMeta object
	 *
	 */
	public function renderMessageMeta() {
		// remember page's output formatting state
		//$of = $this->page->of();

		// turn on output formatting for our rendering (if it's not already on)
		//if (!$of) $this->page->of(true);

		$out = "
		<p>
		$this->subject<br>
		$this->sendstatus<br>
		$this->recipients<br>
		$this->sent<br>
		$this->started<br>
		$this->finished
		</p>
		";

		//if (!$of) $this->page->of(false); 
		return $out; 
	}

	/**
	 * Return a string representing this MessageMeta object
	 *
	 */
	public function __toString() {
		return $this->renderMessageMeta();
	}

}

 

InputfieldMessageMeta.module.php

Spoiler

<?php namespace ProcessWire;

/**
 * Inputfield that provides form fields for GroupMailer FieldtypeMessageMeta
 * (some of the form fields are type="hidden")
 *
 */

class InputfieldMessageMeta extends Inputfield {

    public static function getModuleInfo() {
        return array(
            'title' => 'GroupMailer MessageMeta',
            'version' => 1,
            'summary' => 'Provides form input for the GroupMailer MessageMeta Fieldtype (some fields not visible!).',
            'requires' => 'FieldtypeMessageMeta',  // needed to be uninstalled with FieldtypeMessageMeta!
        );
    }

    const subjectDefaultMaxlength = 2048;

    protected $page;    
    protected $field;

    /**
     * Construct
     * 
     * @throws WireException
     * 
     */
    public function __construct() {
        parent::__construct();
        $this->setAttribute('maxlength', $this->getSubjectDefaultMaxlength()); 
        $this->setAttribute('placeholder', '');
        $this->set('requiredAttr', 0);
        $this->set('stripTags', false); // strip tags from input?

        // if multi-language, support placeholders for each language
        $languages = $this->wire('languages');
        if ($languages) foreach ($languages as $language) {
            // set to blank value so that Field::getInputfield() will recogize this setting is for InputfieldText 
            if (!$language->isDefault()) $this->set("placeholder$language", '');
        }
    }

    /**
     * Initialize this Inputfield
     *
     */
    public function init() {
        parent::init();
        require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'MessageMeta.php'; 
    }

    public function setPage(Page $page) {
        $this->page = $page; 
    }

    public function setField(Field $field) {
        $this->field = $field;
    }

    /**
     * Get the default maxlength attribute value for subject
     * 
     * @return mixed
     * 
     */
    public function getSubjectDefaultMaxlength() {
        return self::subjectDefaultMaxlength;
    }

    /**
     * @param array|string $key
     * @param array|int|string $value
     * @return $this
     * @throws WireException
     */
    public function setAttribute($key, $value) {
        if ($key == 'value' && !$value instanceof MessageMeta && !is_null($value)) {
            throw new WireException('Value should be an instance of MessageMeta');
        }

        return parent::setAttribute($key, $value);
    }

    /**
     * Render the entire input area for MessageMeta
     * 
     * @return string
     *
     */
    public function ___render() {

        $id = $this->attr('id'); 
        $name = $this->attr('name'); 
        $messagemeta = $this->attr('value');

        $subject = $this->sanitizer->entities($messagemeta->subject);
        //$started = $messagemeta->started > 0 ? date(MessageMeta::dateFormat, $messagemeta->started) : '';
        //$finished = $messagemeta->finished > 0 ? date(MessageMeta::dateFormat, $messagemeta->finished) : '';

        $out = "
        <input type='text' id='_{$id}_subject' name='_{$name}_subject' value='{$subject}' class='uk-input'>
        <input type='hidden' id='_{$id}_sendstatus' name='_{$name}_sendstatus' value='{$messagemeta->sendstatus}'>
        <input type='hidden' id='_{$id}_recipients' name='_{$name}_recipients' value='{$messagemeta->recipients}'>
        <input type='hidden' id='_{$id}_sent' name='_{$name}_sent' value='{$messagemeta->sent}'>
        <input type='hidden' id='_{$id}_started' name='_{$name}_started' value='{$messagemeta->started}'>
        <input type='hidden' id='_{$id}_finished' name='_{$name}_finished' value='{$messagemeta->finished}'>
        ";

        return $out;
    }

    /**
     * Process the input after a form submission
     *
     * @param WireInputData $input
     * @return $this
     * @throws WireException
     * 
     */
    public function ___processInput(WireInputData $input) {

        if (!$this->page || !$this->field) {
            throw new WireException("This inputfield requires that you set valid 'page' and 'field' properties to it."); 
        }

        $name = $this->attr('name'); 
        $messagemeta = $this->field->type->getBlankValue($this->page, $this->field); 

        //if (!isset($input->$name)) return $this; // TODO: ???

        $messagemeta = new MessageMeta();
        $messagemeta->subject = $input["_{$name}_subject"];
        $messagemeta->sendstatus = $input["_{$name}_sendstatus"];
        $messagemeta->recipients = $input["_{$name}_recipients"];
        $messagemeta->sent = $input["_{$name}_sent"];
        $messagemeta->started = $input["_{$name}_started"];
        $messagemeta->finished = $input["_{$name}_finished"];
        
        // if the processed MessageMeta subject is different from the previous,
        // then flag this Inputfield as changed so that it will be automatically saved with the page
        // TODO: ???
        if ($messagemeta->subject != $this->value->subject) {
            $this->attr('value', $messagemeta); 
            $this->trackChange('value'); 
        }

        return $this;
    }

    /**
     * Output API Notes
     *
     * @return $inputfields
     */
    public function ___getConfigInputfields() {
        $inputfields = parent::___getConfigInputfields();

        /** @var InputfieldMarkup $field */
        $field = $this->modules->get('InputfieldMarkup'); 
        $field->label = $this->_('API Notes'); 
        $field->description = $this->_('Individual values from this field can be get using the following from your template files:');
        $field->value = 
            "<pre>" .
            "\$page->{$this->name}->subject" . PHP_EOL .
            "\$page->{$this->name}->sendstatus" . PHP_EOL .
            "\$page->{$this->name}->recipients" . PHP_EOL .
            "\$page->{$this->name}->sent" . PHP_EOL .
            "\$page->{$this->name}->started" . PHP_EOL .
            "\$page->{$this->name}->finished" . PHP_EOL .
            "</pre>";
    
        $inputfields->add($field); 

        return $inputfields; 	
    }

}

 

 

 

 

Link to comment
Share on other sites

I'm not really getting what the question is, although I haven't looked at your code in full.

4 hours ago, Gadgetto said:

Comparing with other multi-language fields doesn't help because they are all single-value fields.

Are you sure about this? What do you mean by multi-language fields? Maybe install the multi-lingual site profile and see how things work? For instance, the field images works fine as a multi-language field and it is a multi-value field. I think I'm just not getting the question.

4 hours ago, Gadgetto said:

Here are the full sources of my multi-value input field:

if you Fieldtype is meant to store multiple values for the same page (meaning multiple rows in its database table for the same field on the same page) , then this code is not correct:

4 hours ago, Gadgetto said:

class FieldtypeMessageMeta extends Fieldtype {

In that case, you'll need to extend FieldtypeMulti. However, I may have misunderstood what you mean by multiple values. For instance, if you meant multiple database columns on a single db row, that's something different.

  • Like 1
Link to comment
Share on other sites

2 hours ago, Gadgetto said:

Here are the full sources of my multi-value input field:

Hi @Gadgetto I am sorry for not being able to help but could you please make your thread more readable by "hiding" long code in "Spoiler" blocks (which can be added by clicking on the "eye icon" in the toolbar of this RTE)? 

Link to comment
Share on other sites

10 hours ago, szabesz said:

Hi @Gadgetto I am sorry for not being able to help but could you please make your thread more readable by "hiding" long code in "Spoiler" blocks (which can be added by clicking on the "eye icon" in the toolbar of this RTE)? 

Sorry for that, I was indeed searching for the "Reveal hidden contents" feature as I saw this in other post. Just couldn't find it. Thanks!

  • Thanks 1
Link to comment
Share on other sites

12 hours ago, kongondo said:

Are you sure about this? What do you mean by multi-language fields? Maybe install the multi-lingual site profile and see how things work? For instance, the field images works fine as a multi-language field and it is a multi-value field. I think I'm just not getting the question.

I don't know how to describe it better. I thought multi-language field is a common term for fields which provide input for different languages (like the InputfieldText/InpufieldTextLanguage).

12 hours ago, kongondo said:

if you Fieldtype is meant to store multiple values for the same page (meaning multiple rows in its database table for the same field on the same page) , then this code is not correct:

My Fieldtype is a multi-columns field not multi-rows, therefore FieldtypeMulti won't work (as it is only for multi-row fields).

As I explained in my initial post, I created a Fieldtype which has these columns:

$schema['data'] = 'text NOT NULL'; // we're using 'data' to represent our 'subject' field
$schema['sendstatus'] = 'tinyint NOT NULL DEFAULT 0'; // message send status
$schema['recipients'] = "int(10) unsigned NOT NULL DEFAULT 0";  // recipients counter
$schema['sent'] = "int(10) unsigned NOT NULL DEFAULT 0";  // sent counter
$schema['started'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending start
$schema['finished'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending finished

The "data" col is a text field and I'd like to provide language support for this col only! (the other columns send status, recipients, ... arte integer fields and don't need language support).

I simply can't find an example Fieldtype which provides similar functionality.

Link to comment
Share on other sites

3 hours ago, Gadgetto said:

I simply can't find an example Fieldtype which provides similar functionality.

There is one actually, sort of. Look at the 'images' field in a multilingual setup. 

As you are aware, for truly (I use this word reservedly) multilingual fields, each language has to have its own column. That makes it easy to search the columns in the language you want. However, in your case, since you want only one column to have multilingual features, you have two choices ( + a 3rd not very good one):

  1. Go the route of images fields. In a multilingual setup, the description column of an image field holds each languages' value, saved as JSON. E.g. {"0":"We're gonna need a bigger terminal.","1012":"Wir brauchen einen größeren Terminal.","1013":"Me tarvitsemme isomman päätteen."}. The index of the values are the language ID. In this case, 0= English, 1012=German and 1013=Finnish.The trade off here is searching in one language is limited.
  2. Change your database design pattern. No need to cram things in if they don't fit ?. Let your subject be its own multilingual field and let your other single value data live in their own non-multilingual field. Nothing wrong with that. 
  3. I mention this 3rd option hesitantly. Stick with one field as your are doing but for your data (subject) column create a lookup table for other languages. I am no DB guru but the little I know tells me this 3rd option is not a good design at all.
Edited by kongondo
  • Like 3
Link to comment
Share on other sites

@kongondo Thank you for your hints. In general, I'd like to follow the standard behavior of ProcessWire as closely as possible. So having own "data" columns for each  language would be the best.  I have a Fieldtype "FieldtypeMessageMeta" and try to extend it with "FieldtypeMessageMetaLanguage" - just like other language capable field types do. So an admin can switch between normal Fieldtype and language Fieldtype if necessary.

the longer I think about it, the more uncertain I am if the message subject has to be multilingual at all.

Is it common to automatically send multilingual newsletters? Or is it better to send a separate newsletter for each language?

 

Link to comment
Share on other sites

4 hours ago, Gadgetto said:

Is it common to automatically send multilingual newsletters? Or is it better to send a separate newsletter for each language?

I might have seen a multilingual newsletter once in my life but I might be wrong, so in my experience multilingual newsletters are very rare.

  • Like 2
Link to comment
Share on other sites

1 minute ago, szabesz said:

I might have seen a multilingual newsletter once in my life but I might be wrong, so in my experience multilingual newsletters are very rare.

I also think so. So I'll let the field be single-language for now and if there are requests to add multilingual feature it can be added later.

  • Like 2
Link to comment
Share on other sites

On 2/27/2019 at 5:34 PM, Gadgetto said:

So I'll let the field be single-language for now and if there are requests to add multilingual feature it can be added later.

Just had a thought, so revisiting this. Going by your screenshot, it seems to me that each message is a unique page, no? If that is the case then you may have your cake and eat it too if instead of storing a message's subject in the 'data' column of your Fieldtype, let it be the page's title? This means for multilingual sites, they can decide to have multilingual messages by using language field title and language textarea (assuming that is where the message body is). You would then need to use 'data' to store something else.

 

Just my 2p.

  • Like 1
Link to comment
Share on other sites

12 hours ago, kongondo said:

Just had a thought, so revisiting this. Going by your screenshot, it seems to me that each message is a unique page, no? If that is the case then you may have your cake and eat it too if instead of storing a message's subject in the 'data' column of your Fieldtype, let it be the page's title? This means for multilingual sites, they can decide to have multilingual messages by using language field title and language textarea (assuming that is where the message body is). You would then need to use 'data' to store something else.

You are right! Each message is a unique page. To be flexible, I decided not to use the title field as email subject. GroupMailer will be designed so that each page can be a message. It will only be necessary to add a custom field (FieldtypeMessageMeta) to the corresponding template.
This field type will include the subject field. So it is possible that pages have separate titles and email subject.

Link to comment
Share on other sites

25 minutes ago, Gadgetto said:

GroupMailer will be designed so that each page can be a message. It will only be necessary to add a custom field (FieldtypeMessageMeta) to the corresponding template.
This field type will include the subject field. So it is possible that pages have separate titles and email subject.

Gotcha.

Link to comment
Share on other sites

Based on your description I feel like you're tackling the problem from the wrong end. You're actually trying to do what was requested from MarkupSEO ages ago: Just adding a single field to a template, but getting a whole bunch of fields in the process. In my opinion either let people add all fields to their templates  or even better have an own template and just store the metadata there. Sadly it took quite a few month until the latter option was actually added properly, but now we have it: 

https://processwire.com/blog/posts/processwire-3.0.73-and-new-fieldset-types/#fieldset-page-fieldtypefieldsetpage
https://processwire.com/blog/posts/processwire-3.0.74-adds-new-fieldsetpage-field-type/

Edit:

And some more context from the time I initially proposed the idea (I've no idea if Ryan actually knew this, but it's basically what I proposed): 

 

  • Like 2
Link to comment
Share on other sites

@LostKobrakai FieldtypeFieldsetPage looks great - didn't even know this exists! Thanks for this hint!

Using a template for configuring/defining a GroupMailer message page was my initial intention. But wouldn't that be too inflexible? Wouldn't it be better if the dev could use any template he likes just by adding a single field? It would also allow to "convert" any existing page to a GroupMailer Message on the fly.

I'd like to hold the Module as flexible as possible but also easy to setup. The longer I work on the project, the more uncertain I will be about the implementation...

Link to comment
Share on other sites

That's what FieldtypeFieldsetPage would allow you to do. Create the field on installation of your module (and update/delete it from your module; It's not to be edited by the user) and the user would just need to add that field to whatever template needed.

Link to comment
Share on other sites

Just now, LostKobrakai said:

That's what FieldtypeFieldsetPage would allow you to do. Create the field on installation of your module (and update/delete it from your module; It's not to be edited by the user) and the user would just need to add that field to whatever template needed.

I need to have a look at this Fieldtype and how this is implemented. I currently don't fully understand how this is meant to be used.

Link to comment
Share on other sites

GroupMailer needs a lot of meta data like some send counters, status-flags, send/finished dates and so on - all meta fields are hidden fields only and not editable by the user. The custom Fieldtype I created has all these columns packed in one field. Using  FieldtypeFieldsetPage would mean that I create separate fields for each of these meta columns (if I understand right). Wouldn't this have a performance impact. Instead of using a single (multi-column) field I'd need to use many separate fields.

Link to comment
Share on other sites

Sure has, but it's what you do with most everything else in processwire as well. Are the read/write patterns really that demanding that it would matter? 

But if only one field of yours is multi-language I feel like it should be quite possible to adapt existing patterns from other fieldtypes. 

Link to comment
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
 Share

×
×
  • Create New...