<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Step class.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours;
use context_system;
use stdClass;
defined('MOODLE_INTERNAL') || die();
/**
* Step class.
*
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class step {
/**
* @var int $id The id of the step.
*/
protected $id;
/**
* @var int $tourid The id of the tour that this step belongs to.
*/
protected $tourid;
/**
* @var tour $tour The tour class that this step belongs to.
*/
protected $tour;
/**
* @var string $title The title of the step.
*/
protected $title;
/**
* @var string $content The content of this step.
*/
protected $content;
/**
* @var int $contentformat The content format: FORMAT_MOODLE/FORMAT_HTML/FORMAT_PLAIN/FORMAT_MARKDOWN.
*/
protected $contentformat;
/**
* @var int $targettype The type of target.
*/
protected $targettype;
/**
* @var string $targetvalue The value for this type of target.
*/
protected $targetvalue;
/**
* @var int $sortorder The sort order.
*/
protected $sortorder;
/**
* @var object $config The configuration as an object.
*/
protected $config;
/**
* @var bool $dirty Whether the step has been changed since it was loaded
*/
protected $dirty = false;
/**
* @var bool $isimporting Whether the step is being imported or not.
*/
protected $isimporting;
/**
* @var stdClass[] $files The list of attached files for this step.
*/
protected $files = [];
/**
* Fetch the step instance.
*
* @param int $id The id of the step to be retrieved.
* @return step
*/
public static function instance($id) {
$step = new step();
return $step->fetch($id);
}
/**
* Load the step instance.
*
* @param stdClass $record The step record to be loaded.
* @param bool $clean Clean the values.
* @param bool $isimporting Whether the step is being imported or not.
* @return step
*/
public static function load_from_record($record, $clean = false, bool $isimporting = false) {
$step = new self();
$step->set_importing($isimporting);
return $step->reload_from_record($record, $clean);
}
/**
* Fetch the step instance.
*
* @param int $id The id of the step to be retrieved.
* @return step
*/
protected function fetch($id) {
global $DB;
return $this->reload_from_record(
$DB->get_record('tool_usertours_steps', array('id' => $id))
);
}
/**
* Refresh the current step from the datbase.
*
* @return step
*/
protected function reload() {
return $this->fetch($this->id);
}
/**
* Reload the current step from the supplied record.
*
* @param stdClass $record The step record to be loaded.
* @param bool $clean Clean the values.
* @return step
*/
protected function reload_from_record($record, $clean = false) {
$this->id = $record->id;
$this->tourid = $record->tourid;
if ($clean) {
$this->title = clean_param($record->title, PARAM_TEXT);
$this->content = clean_text($record->content);
} else {
$this->title = $record->title;
$this->content = $record->content;
}
$this->contentformat = isset($record->contentformat) ? $record->contentformat : FORMAT_MOODLE;
$this->targettype = $record->targettype;
$this->targetvalue = $record->targetvalue;
$this->sortorder = $record->sortorder;
$this->config = json_decode($record->configdata);
$this->dirty = false;
if ($this->isimporting && isset($record->files)) {
// We are importing/exporting the step.
$this->files = $record->files;
}
return $this;
}
/**
* Set the import state for the step.
*
* @param bool $isimporting True if the step is imported, otherwise false.
* @return void
*/
protected function set_importing(bool $isimporting = false): void {
$this->isimporting = $isimporting;
}
/**
* Get the ID of the step.
*
* @return int
*/
public function get_id() {
return $this->id;
}
/**
* Get the Tour ID of the step.
*
* @return int
*/
public function get_tourid() {
return $this->tourid;
}
/**
* Get the Tour instance that this step belongs to.
*
* @return tour
*/
public function get_tour() {
if ($this->tour === null) {
$this->tour = tour::instance($this->tourid);
}
return $this->tour;
}
/**
* Set the id of the tour.
*
* @param int $value The id of the tour.
* @return self
*/
public function set_tourid($value) {
$this->tourid = $value;
$this->tour = null;
$this->dirty = true;
return $this;
}
/**
* Get the Title of the step.
*
* @return string
*/
public function get_title() {
return $this->title;
}
/**
* Set the title for this step.
*
* @param string $value The new title to use.
* @return $this
*/
public function set_title($value) {
$this->title = clean_text($value);
$this->dirty = true;
return $this;
}
/**
* Get the content format of the step.
*
* @return int
*/
public function get_contentformat(): int {
return $this->contentformat;
}
/**
* Get the body content of the step.
*
* @return string
*/
public function get_content() {
return $this->content;
}
/**
* Set the content value for this step.
*
* @param string $value The new content to use.
* @param int $format The new format to use: FORMAT_MOODLE/FORMAT_HTML/FORMAT_PLAIN/FORMAT_MARKDOWN.
* @return $this
*/
public function set_content($value, $format = FORMAT_HTML) {
$this->content = clean_text($value);
$this->contentformat = $format;
$this->dirty = true;
return $this;
}
/**
* Get the content value for this step.
*
* @return string
*/
public function get_targettype() {
return $this->targettype;
}
/**
* Set the type of target for this step.
*
* @param string $value The new target to use.
* @return $this
*/
public function set_targettype($value) {
$this->targettype = $value;
$this->dirty = true;
return $this;
}
/**
* Get the target value for this step.
*
* @return string
*/
public function get_targetvalue() {
return $this->targetvalue;
}
/**
* Set the target value for this step.
*
* @param string $value The new target value to use.
* @return $this
*/
public function set_targetvalue($value) {
$this->targetvalue = $value;
$this->dirty = true;
return $this;
}
/**
* Get the target instance for this step.
*
* @return target
*/
public function get_target() {
return target::get_target_instance($this);
}
/**
* Get the current sortorder for this step.
*
* @return int
*/
public function get_sortorder() {
return (int) $this->sortorder;
}
/**
* Whether this step is the first step in the tour.
*
* @return boolean
*/
public function is_first_step() {
return ($this->get_sortorder() === 0);
}
/**
* Whether this step is the last step in the tour.
*
* @return boolean
*/
public function is_last_step() {
$stepcount = $this->get_tour()->count_steps();
return ($this->get_sortorder() === $stepcount - 1);
}
/**
* Set the sortorder for this step.
*
* @param int $value The new sortorder to use.
* @return $this
*/
public function set_sortorder($value) {
$this->sortorder = $value;
$this->dirty = true;
return $this;
}
/**
* Get the link to move this step up in the sortorder.
*
< * @return moodle_url
> * @return \moodle_url
*/
public function get_moveup_link() {
return helper::get_move_step_link($this->get_id(), helper::MOVE_UP);
}
/**
* Get the link to move this step down in the sortorder.
*
< * @return moodle_url
> * @return \moodle_url
*/
public function get_movedown_link() {
return helper::get_move_step_link($this->get_id(), helper::MOVE_DOWN);
}
/**
* Get the value of the specified configuration item.
*
* If notvalue was found, and no default was specified, the default for the tour will be used.
*
* @param string $key The configuration key to set.
* @param mixed $default The default value to use if a value was not found.
* @return mixed
*/
public function get_config($key = null, $default = null) {
if ($this->config === null) {
$this->config = (object) array();
}
if ($key === null) {
return $this->config;
}
if ($this->get_targettype() !== null) {
$target = $this->get_target();
if ($target->is_setting_forced($key)) {
return $target->get_forced_setting_value($key);
}
}
if (property_exists($this->config, $key)) {
return $this->config->$key;
}
if ($default !== null) {
return $default;
}
return $this->get_tour()->get_config($key);
}
/**
* Set the configuration item as specified.
*
* @param string $key The configuration key to set.
* @param mixed $value The new value for the configuration item.
* @return $this
*/
public function set_config($key, $value) {
if ($this->config === null) {
$this->config = (object) array();
}
if ($value === null) {
unset($this->config->$key);
} else {
$this->config->$key = $value;
}
$this->dirty = true;
return $this;
}
/**
* Get the edit link for this step.
*
< * @return moodle_url
> * @return \moodle_url
*/
public function get_edit_link() {
return helper::get_edit_step_link($this->tourid, $this->id);
}
/**
* Get the delete link for this step.
*
< * @return moodle_url
> * @return \moodle_url
*/
public function get_delete_link() {
return helper::get_delete_step_link($this->id);
}
/**
* Embed attached file to the json file for step.
*
* @return array List of files.
*/
protected function embed_files(): array {
$systemcontext = context_system::instance();
$fs = get_file_storage();
$areafiles = $fs->get_area_files($systemcontext->id, 'tool_usertours', 'stepcontent', $this->id);
$files = [];
foreach ($areafiles as $file) {
if ($file->is_directory()) {
continue;
}
$files[] = [
'name' => $file->get_filename(),
'path' => $file->get_filepath(),
'content' => base64_encode($file->get_content()),
'encode' => 'base64'
];
}
return $files;
}
/**
* Get the embed files information and create store_file for this step.
*
* @return void
*/
protected function extract_files() {
$fs = get_file_storage();
$systemcontext = context_system::instance();
foreach ($this->files as $file) {
$filename = $file->name;
$filepath = $file->path;
$filecontent = $file->content;
$filerecord = [
'contextid' => $systemcontext->id,
'component' => 'tool_usertours',
'filearea' => 'stepcontent',
'itemid' => $this->get_id(),
'filepath' => $filepath,
'filename' => $filename,
];
$fs->create_file_from_string($filerecord, base64_decode($filecontent));
}
}
/**
* Prepare this step for saving to the database.
*
* @param bool $isexporting Whether the step is being exported or not.
* @return object
*/
public function to_record(bool $isexporting = false) {
$record = [
'id' => $this->id,
'tourid' => $this->tourid,
'title' => $this->title,
'content' => $this->content,
'contentformat' => $this->contentformat,
'targettype' => $this->targettype,
'targetvalue' => $this->targetvalue,
'sortorder' => $this->sortorder,
'configdata' => json_encode($this->config),
];
if ($isexporting) {
// We are exporting the step, adding files node to the json record.
$record['files'] = $this->embed_files();
}
return (object) $record;
}
/**
* Calculate the next sort-order value.
*
* @return int
*/
protected function calculate_sortorder() {
$count = $this->get_tour()->count_steps();
$this->sortorder = $count;
return $this;
}
/**
* Save the tour and it's configuration to the database.
*
* @param boolean $force Whether to force writing to the database.
* @return $this
*/
public function persist($force = false) {
global $DB;
if (!$this->dirty && !$force) {
return $this;
}
if ($this->id) {
$record = $this->to_record();
$DB->update_record('tool_usertours_steps', $record);
} else {
$this->calculate_sortorder();
$record = $this->to_record();
unset($record->id);
$this->id = $DB->insert_record('tool_usertours_steps', $record);
$this->get_tour()->reset_step_sortorder();
}
$systemcontext = context_system::instance();
if ($draftid = file_get_submitted_draft_itemid('content')) {
// Take any files added to the stepcontent draft file area and
// convert them into the proper event description file area. Also
// parse the content text and replace the URLs to the draft files
// with the @@PLUGIN_FILE@@ placeholder to be persisted in the DB.
$this->content = file_save_draft_area_files(
$draftid,
$systemcontext->id,
'tool_usertours',
'stepcontent',
$this->id,
['subdirs' => true],
$this->content
);
$DB->set_field('tool_usertours_steps', 'content', $this->content, ['id' => $this->id]);
}
if ($this->isimporting) {
// We are importing the step, we need to create store_file from the json record.
$this->extract_files();
}
$this->reload();
// Notify of a change to the step configuration.
// This must be done separately to tour change notifications.
cache::notify_step_change($this->get_tourid());
// Notify the cache that a tour has changed.
// Tours are only stored in the cache if there are steps.
// If there step count has changed for some reason, this will change the potential cache results.
cache::notify_tour_change();
return $this;
}
/**
* Remove this step.
*/
public function remove() {
global $DB;
if ($this->id === null) {
return;
}
$DB->delete_records('tool_usertours_steps', array('id' => $this->id));
$this->get_tour()->reset_step_sortorder();
// Notify of a change to the step configuration.
// This must be done separately to tour change notifications.
cache::notify_step_change($this->get_id());
// Notify the cache that a tour has changed.
// Tours are only stored in the cache if there are steps.
// If there step count has changed for some reason, this will change the potential cache results.
cache::notify_tour_change();
}
/**
* Get the list of possible placement options.
*
* @return array
*/
public function get_placement_options() {
return configuration::get_placement_options(true);
}
/**
* The list of possible configuration keys.
*
* @return array
*/
public static function get_config_keys() {
return [
'placement',
'orphan',
'backdrop',
'reflex',
];
}
/**
* Add the step configuration to the form.
*
< * @param MoodleQuickForm $mform The form to add configuration to.
> * @param \MoodleQuickForm $mform The form to add configuration to.
* @return $this
*/
public function add_config_to_form(\MoodleQuickForm $mform) {
$tour = $this->get_tour();
$options = configuration::get_placement_options($tour->get_config('placement'));
$mform->addElement('select', 'placement', get_string('placement', 'tool_usertours'), $options);
$mform->addHelpButton('placement', 'placement', 'tool_usertours');
$this->add_config_field_to_form($mform, 'orphan');
$this->add_config_field_to_form($mform, 'backdrop');
$this->add_config_field_to_form($mform, 'reflex');
return $this;
}
/**
* Add the specified step field configuration to the form.
*
< * @param MoodleQuickForm $mform The form to add configuration to.
> * @param \MoodleQuickForm $mform The form to add configuration to.
* @param string $key The key to add.
* @return $this
*/
public function add_config_field_to_form(\MoodleQuickForm $mform, $key) {
$tour = $this->get_tour();
$default = (bool) $tour->get_config($key);
$options = [
true => get_string('yes'),
false => get_string('no'),
];
if (!isset($options[$default])) {
$default = configuration::get_default_value($key);
}
$options = array_reverse($options, true);
$options[configuration::TOURDEFAULT] = get_string('defaultvalue', 'tool_usertours', $options[$default]);
$options = array_reverse($options, true);
$mform->addElement('select', $key, get_string($key, 'tool_usertours'), $options);
$mform->setDefault($key, configuration::TOURDEFAULT);
$mform->addHelpButton($key, $key, 'tool_usertours');
return $this;
}
/**
* Prepare the configuration data for the moodle form.
*
* @return object
*/
public function prepare_data_for_form() {
$data = $this->to_record();
foreach (self::get_config_keys() as $key) {
$data->$key = $this->get_config($key, configuration::get_step_default_value($key));
}
if ($this->get_targettype() !== null) {
$this->get_target()->prepare_data_for_form($data);
}
// Prepare content for editing in a form 'editor' field type.
$draftitemid = file_get_submitted_draft_itemid('tool_usertours');
$systemcontext = context_system::instance();
$data->content = [
'format' => $data->contentformat,
'itemid' => $draftitemid,
'text' => file_prepare_draft_area(
$draftitemid,
$systemcontext->id,
'tool_usertours',
'stepcontent',
$this->id,
['subdirs' => true],
$data->content
),
];
return $data;
}
/**
* Handle submission of the step editing form.
*
* @param local\forms\editstep $mform The sumitted form.
* @param stdClass $data The submitted data.
* @return $this
*/
public function handle_form_submission(local\forms\editstep &$mform, stdClass $data) {
$this->set_title($data->title);
$this->set_content($data->content['text'], $data->content['format']);
$this->set_targettype($data->targettype);
$this->set_targetvalue($this->get_target()->get_value_from_form($data));
foreach (self::get_config_keys() as $key) {
if (!$this->get_target()->is_setting_forced($key)) {
if (isset($data->$key)) {
$value = $data->$key;
} else {
$value = configuration::TOURDEFAULT;
}
if ($value === configuration::TOURDEFAULT) {
$this->set_config($key, null);
} else {
$this->set_config($key, $value);
}
}
}
$this->persist();
return $this;
}
/**
* Attempt to fetch any matching langstring if the string is in the
* format identifier,component.
*
* @deprecated since Moodle 4.0 MDL-72783. Please use helper::get_string_from_input() instead.
* @param string $string
* @return string
*/
public static function get_string_from_input($string) {
debugging('Use of ' . __FUNCTION__ .
'() have been deprecated, please update your code to use helper::get_string_from_input()', DEBUG_DEVELOPER);
return helper::get_string_from_input($string);
}
/**
* Attempt to replace PIXICON placeholder with the correct images for tour step content.
*
* @param string $content Tour content
* @return string Processed tour content
*/
public static function get_step_image_from_input(string $content): string {
if (strpos($content, '@@PIXICON') === false) {
return $content;
}
$content = preg_replace_callback('%@@PIXICON::(?P<identifier>([^::]*))::(?P<component>([^@@]*))@@%',
function(array $matches) {
global $OUTPUT;
$component = $matches['component'];
if ($component == 'moodle') {
$component = 'core';
}
return \html_writer::img($OUTPUT->image_url($matches['identifier'], $component)->out(false), '',
['class' => 'img-fluid']);
},
$content
);
return $content;
}
}