ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç PK'9\X ~~selfcompletion/db/install.phpnu[. /** * Selfcompletion block installation. * * @package block_selfcompletion * @copyright 2021 Amaia Anabitarte * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Perform the post-install procedures. */ function xmldb_block_selfcompletion_install() { global $DB; // Disable selfcompletion on new installs by default. $DB->set_field('block', 'visible', 0, ['name' => 'selfcompletion']); } PK'9\Bwselfcompletion/db/access.phpnu[. /** * Self completion block caps. * * @package block_selfcompletion * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/selfcompletion:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\a;selfcompletion/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the self completion block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @since Moodle 2.0 * @package block_selfcompletion * @copyright 2012 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Handles upgrading instances of this block. * * @param int $oldversion * @param object $block */ function xmldb_block_selfcompletion_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\Dà/selfcompletion/lang/en/block_selfcompletion.phpnu[. /** * Strings for component 'block_selfcompletion', language 'en', branch 'MOODLE_20_STABLE' * * @package block_selfcompletion * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['alreadyselfcompleted'] = 'You have already marked yourself as complete in this course'; $string['completecourse'] = 'Complete course'; $string['pluginname'] = 'Self completion'; $string['selfcompletionnotenabled'] = 'The self completion criteria has not been enabled for this course'; $string['selfcompletion:addinstance'] = 'Add a new self completion block'; $string['privacy:metadata'] = 'The Self completion block only shows data stored in other locations.'; PK'9\bb'selfcompletion/block_selfcompletion.phpnu[. /** * Self completion block. * * @package block_selfcompletion * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once($CFG->libdir.'/completionlib.php'); /** * Self course completion marking * Let's a user manually complete a course * * Will only display if the course has completion enabled, * there is a self completion criteria, and the logged in user is yet * to complete the course. */ class block_selfcompletion extends block_base { public function init() { $this->title = get_string('pluginname', 'block_selfcompletion'); } function applicable_formats() { return array('course' => true); } public function get_content() { global $CFG, $USER; // If content is cached if ($this->content !== NULL) { return $this->content; } // Create empty content $this->content = new stdClass; // Can edit settings? $can_edit = has_capability('moodle/course:update', context_course::instance($this->page->course->id)); // Get course completion data $info = new completion_info($this->page->course); // Don't display if completion isn't enabled! if (!completion_info::is_enabled_for_site()) { if ($can_edit) { $this->content->text = get_string('completionnotenabledforsite', 'completion'); } return $this->content; } else if (!$info->is_enabled()) { if ($can_edit) { $this->content->text = get_string('completionnotenabledforcourse', 'completion'); } return $this->content; } // Get this user's data $completion = $info->get_completion($USER->id, COMPLETION_CRITERIA_TYPE_SELF); // Check if self completion is one of this course's criteria if (empty($completion)) { if ($can_edit) { $this->content->text = get_string('selfcompletionnotenabled', 'block_selfcompletion'); } return $this->content; } // Check this user is enroled if (!$info->is_tracked_user($USER->id)) { $this->content->text = get_string('nottracked', 'completion'); return $this->content; } // Is course complete? if ($info->is_course_complete($USER->id)) { $this->content->text = get_string('coursealreadycompleted', 'completion'); return $this->content; // Check if the user has already marked themselves as complete } else if ($completion->is_complete()) { $this->content->text = get_string('alreadyselfcompleted', 'block_selfcompletion'); return $this->content; // If user is not complete, or has not yet self completed } else { $this->content->text = ''; $this->content->footer = '
'; $this->content->footer .= get_string('completecourse', 'block_selfcompletion').'...'; } return $this->content; } } PK'9\oV%selfcompletion/version.phpnu[. /** * Version details * * @package block_selfcompletion * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_selfcompletion'; // Full name of the plugin (used for diagnostics) PK'9\F+selfcompletion/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_selfcompletion. * * @package block_selfcompletion * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_selfcompletion\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_selfcompletion implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\brsscourse_summary/styles.cssnu[.block_course_summary .content { padding: 10px; } .block_course_summary .editbutton { text-align: right; }PK'9\N~~course_summary/db/install.phpnu[. /** * Course summary block installation. * * @package block_course_summary * @copyright 2021 Amaia Anabitarte * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Perform the post-install procedures. */ function xmldb_block_course_summary_install() { global $DB; // Disable course_summary on new installs by default. $DB->set_field('block', 'visible', 0, ['name' => 'course_summary']); } PK'9\gŤcourse_summary/db/access.phpnu[. /** * Course summary block caps. * * @package block_course_summary * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/course_summary:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\5course_summary/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the course summary block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @since Moodle 2.0 * @package block_course_summary * @copyright 2012 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Handles upgrading instances of this block. * * @param int $oldversion * @param object $block */ function xmldb_block_course_summary_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\ /course_summary/lang/en/block_course_summary.phpnu[. /** * Strings for component 'block_course_summary', language 'en', branch 'MOODLE_20_STABLE' * * @package block_course_summary * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['coursesummary'] = 'Course summary'; $string['course_summary:addinstance'] = 'Add a new course/site summary block'; $string['pluginname'] = 'Course/site summary'; $string['privacy:metadata'] = 'The Course/site summary block only shows information about courses and does not store data itself.'; PK'9\~course_summary/version.phpnu[. /** * Version details * * @package block_course_summary * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_course_summary'; // Full name of the plugin (used for diagnostics) PK'9\c+course_summary/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_course_summary. * * @package block_course_summary * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_course_summary\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_course_summary implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\[zJN N 'course_summary/block_course_summary.phpnu[. /** * Course summary block * * @package block_course_summary * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_course_summary extends block_base { /** * @var bool Flag to indicate whether the header should be hidden or not. */ private $headerhidden = true; function init() { $this->title = get_string('pluginname', 'block_course_summary'); } function applicable_formats() { return array('all' => true, 'mod' => false, 'tag' => false, 'my' => false); } function specialization() { // Page type starts with 'course-view' and the page's course ID is not equal to the site ID. if (strpos($this->page->pagetype, PAGE_COURSE_VIEW) === 0 && $this->page->course->id != SITEID) { $this->title = get_string('coursesummary', 'block_course_summary'); $this->headerhidden = false; } } function get_content() { global $CFG, $OUTPUT; require_once($CFG->libdir . '/filelib.php'); if($this->content !== NULL) { return $this->content; } if (empty($this->instance)) { return ''; } $this->content = new stdClass(); $options = new stdClass(); $options->noclean = true; // Don't clean Javascripts etc $options->overflowdiv = true; $context = context_course::instance($this->page->course->id); $this->page->course->summary = file_rewrite_pluginfile_urls($this->page->course->summary, 'pluginfile.php', $context->id, 'course', 'summary', NULL); $this->content->text = format_text($this->page->course->summary, $this->page->course->summaryformat, $options); $this->content->footer = ''; return $this->content; } function hide_header() { return $this->headerhidden; } } PK'9\]>course_summary/tests/behat/block_course_summary_course.featurenu[@block @block_course_summary Feature: Course summary block used in a course In order to help particpants know the summary of a course As a teacher I can add the course summary block to a course page Background: Given the following "courses" exist: | fullname | shortname | summary | category | | Course 1 | C101 | Proved the course summary block works! |0 | And the following "users" exist: | username | firstname | lastname | email | | student1 | Sam | Student | student1@example.com | | teacher1 | Teacher | One | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | student1 | C101 | student | | teacher1 | C101 | editingteacher | And I enable "course_summary" "block" plugin And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | course_summary | Course | C101 | course-view-* | side-pre | Scenario: Student can view course summary When I am on the "Course 1" course page logged in as student1 Then "Course summary" "block" should exist And I should see "Course summary" in the "Course summary" "block" And I should see "Proved the course summary block works!" in the "Course summary" "block" Scenario: Teacher can not see edit icon when edit mode is off When I am on the "Course 1" course page logged in as teacher1 Then I should see "Proved the course summary block works!" in the "Course summary" "block" And I should see "Course summary" in the "Course summary" "block" And "Edit" "link" should not exist in the "Course summary" "block" PK'9\m<}Acourse_summary/tests/behat/block_course_summary_frontpage.featurenu[@block @block_course_summary Feature: Course summary block used on the frontpage In order to help particpants know the summary of a site As admin I can use the course summary block on the frontpage Background: Given I log in as "admin" And I enable "course_summary" "block" plugin And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | course_summary | System | 1 | site-index | side-pre | And I am on site homepage And I navigate to "Site home > Site home settings" in site administration And I set the following fields to these values: | summary | Proved the summary block works! | And I press "Save changes" And I log out # The course summary block a default front page block, so no need to add it. Scenario: Guest can view site summary When I am on site homepage Then "Course/site summary" "block" should exist And I should not see "Course summary" in the "Course/site summary" "block" And I should see "Proved the summary block works!" in the "Course/site summary" "block" Scenario: Admin can not see edit icon when edit mode is off When I log in as "admin" And I am on site homepage Then I should see "Proved the summary block works!" in the "Course/site summary" "block" And I should not see "Course summary" in the "Course/site summary" "block" And "Edit" "link" should not exist in the "Course/site summary" "block" PK'9\}=login/db/access.phpnu[. /** * Login block caps. * * @package block_login * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/login:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\uwȃlogin/lang/en/block_login.phpnu[. /** * Strings for component 'block_login', language 'en', branch 'MOODLE_20_STABLE' * * @package block_login * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['login:addinstance'] = 'Add a new login block'; $string['pluginname'] = 'Login'; $string['privacy:metadata'] = 'The Login block only provides a way to log in and does not store any data itself.'; PK'9\V՛login/version.phpnu[. /** * Version details * * @package block_login * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_login'; // Full name of the plugin (used for diagnostics) PK'9\I!"login/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_login. * * @package block_login * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_login\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_login implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\rYlogin/block_login.phpnu[. /** * Login block * * @package block_login * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_login extends block_base { function init() { $this->title = get_string('pluginname', 'block_login'); } function applicable_formats() { return array('site' => true); } function get_content() { global $USER, $CFG, $SESSION, $OUTPUT; require_once($CFG->libdir . '/authlib.php'); $wwwroot = ''; $signup = ''; if ($this->content !== NULL) { return $this->content; } $wwwroot = $CFG->wwwroot; if (signup_is_enabled()) { $signup = $wwwroot . '/login/signup.php'; } // TODO: now that we have multiauth it is hard to find out if there is a way to change password $forgot = $wwwroot . '/login/forgot_password.php'; $username = get_moodle_cookie(); $this->content = new stdClass(); $this->content->footer = ''; $this->content->text = ''; if (!isloggedin() or isguestuser()) { // Show the block if (empty($CFG->authloginviaemail)) { $strusername = get_string('username'); } else { $strusername = get_string('usernameemail'); } $this->content->text .= "\n".'
'; $this->content->text .= '
'; $this->content->text .= ''; $this->content->text .= 'content->text .= ' class="form-control" value="'.s($username).'" autocomplete="username"/>
'; $this->content->text .= '
'; $this->content->text .= 'content->text .= ' class="form-control" value="" autocomplete="current-password"/>'; $this->content->text .= '
'; // ReCaptcha. if (login_captcha_enabled()) { require_once($CFG->libdir . '/recaptchalib_v2.php'); $this->content->text .= '
'; $this->content->text .= recaptcha_get_challenge_html(RECAPTCHA_API_URL, $CFG->recaptchapublickey, current_language(), true); $this->content->text .= '
'; } $this->content->text .= '
'; $this->content->text .= ''; $this->content->text .= '
'; $this->content->text .= ''; $this->content->text .= "
\n"; if (!empty($signup)) { $this->content->text .= ''; } if (!empty($forgot)) { $this->content->text .= ''; } $authsequence = get_enabled_auth_plugins(); // Get all auths, in sequence. $potentialidps = array(); foreach ($authsequence as $authname) { $authplugin = get_auth_plugin($authname); $potentialidps = array_merge($potentialidps, $authplugin->loginpage_idp_list($this->page->url->out(false))); } if (!empty($potentialidps)) { $this->content->text .= '
'; $this->content->text .= '
' . get_string('potentialidps', 'auth') . '
'; $this->content->text .= ''; $this->content->text .= '
'; } } return $this->content; } } PK'9\,%login/tests/behat/login_block.featurenu[@block @block_login Feature: Login from a block In order to make it easier to login As an user In need to login through a block Background: Given the following "users" exist: | username | password | firstname | lastname | email | | testuser | testpass | Test | User | student1@example.com | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | login | System | 1 | site-index | side-pre | Scenario: Login block visible to non-logged in users When I am on homepage Then "Login" "block" should exist Scenario: Login as student through login block Given I am on homepage When I set the field "Username" to "testuser" And I set the field "Password" to "testpass" And I click on "Log in" "button" in the "Login" "block" Then I should see "You are logged in as Test User" And "Login" "block" should not exist PK'9\*blog_tags/styles.cssnu[.block_blog_tags .s20 { font-size: 1.5em; font-weight: bold; } .block_blog_tags .s19 { font-size: 1.5em; } .block_blog_tags .s18 { font-size: 1.4em; font-weight: bold; } .block_blog_tags .s17 { font-size: 1.4em; } .block_blog_tags .s16 { font-size: 1.3em; font-weight: bold; } .block_blog_tags .s15 { font-size: 1.3em; } .block_blog_tags .s14 { font-size: 1.2em; font-weight: bold; } .block_blog_tags .s13 { font-size: 1.2em; } .block_blog_tags .s12, .block_blog_tags .s11 { font-size: 1.1em; font-weight: bold; } .block_blog_tags .s10, .block_blog_tags .s9 { font-size: 1.1em; } .block_blog_tags .s8, .block_blog_tags .s7 { font-size: 1em; font-weight: bold; } .block_blog_tags .s6, .block_blog_tags .s5 { font-size: 1em; } .block_blog_tags .s4, .block_blog_tags .s3 { font-size: 0.9em; font-weight: bold; } .block_blog_tags .s2, .block_blog_tags .s1 { font-size: 0.9em; }PK'9\   blog_tags/db/access.phpnu[. /** * Blog tags block caps. * * @package block_blog_tags * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/blog_tags:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\%blog_tags/lang/en/block_blog_tags.phpnu[. /** * Strings for component 'block_blog_tags', language 'en', branch 'MOODLE_20_STABLE' * * @package block_blog_tags * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['blog_tags:addinstance'] = 'Add a new blog tags block'; $string['pluginname'] = 'Blog tags'; $string['configtitle'] = 'Blog tags block title'; $string['privacy:metadata'] = 'The Blog tags block only shows data stored in other locations.'; PK'9\& blog_tags/edit_form.phpnu[. /** * Form for editing Blog tags block instances. * * @package block_blog_tags * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Form for editing Blog tags block instances. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_blog_tags_edit_form extends block_edit_form { protected function specific_definition($mform) { // Fields for editing HTML block title and contents. $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $mform->addElement('text', 'config_title', get_string('configtitle', 'block_blog_tags')); $mform->setDefault('config_title', get_string('blogtags', 'blog')); $mform->setType('config_title', PARAM_TEXT); $numberoftags = array(); for($i = 1; $i <= 50; $i++) { $numberoftags[$i] = $i; } $mform->addElement('select', 'config_numberoftags', get_string('numberoftags', 'blog'), $numberoftags); $mform->setDefault('config_numberoftags', BLOCK_BLOG_TAGS_DEFAULTNUMBEROFTAGS); $timewithin = array( 10 => get_string('numdays', '', 10), 30 => get_string('numdays', '', 30), 60 => get_string('numdays', '', 60), 90 => get_string('numdays', '', 90), 120 => get_string('numdays', '', 120), 240 => get_string('numdays', '', 240), 365 => get_string('numdays', '', 365), ); $mform->addElement('select', 'config_timewithin', get_string('timewithin', 'blog'), $timewithin); $mform->setDefault('config_timewithin', BLOCK_BLOG_TAGS_DEFAULTTIMEWITHIN); $sort = array( 'name' => get_string('tagtext', 'blog'), 'id' => get_string('tagdatelastused', 'blog'), ); $mform->addElement('select', 'config_sort', get_string('tagsort', 'blog'), $sort); $mform->setDefault('config_sort', BLOCK_BLOG_TAGS_DEFAULTSORT); } } PK'9\n!blog_tags/version.phpnu[. /** * Version details * * @package block_blog_tags * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_blog_tags'; // Full name of the plugin (used for diagnostics) PK'9\(`!`!blog_tags/block_blog_tags.phpnu[. /** * Blog tags block. * * @package block_blog_tags * @copyright 2006 Shane Elliott * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); define('BLOCK_BLOG_TAGS_DEFAULTTIMEWITHIN', 90); define('BLOCK_BLOG_TAGS_DEFAULTNUMBEROFTAGS', 20); define('BLOCK_BLOG_TAGS_DEFAULTSORT', 'name'); class block_blog_tags extends block_base { function init() { $this->title = get_string('pluginname', 'block_blog_tags'); } function instance_allow_multiple() { return true; } function has_config() { return false; } function applicable_formats() { return array('all' => true, 'my' => false, 'tag' => false); } function instance_allow_config() { return true; } function specialization() { // load userdefined title and make sure it's never empty if (empty($this->config->title)) { $this->title = get_string('pluginname', 'block_blog_tags'); } else { $this->title = $this->config->title; } } function get_content() { global $CFG, $SITE, $USER, $DB, $OUTPUT; if ($this->content !== NULL) { return $this->content; } // make sure blog and tags are actually enabled if (empty($CFG->bloglevel)) { $this->content = new stdClass(); $this->content->text = ''; if ($this->page->user_is_editing()) { $this->content->text = get_string('blogdisable', 'blog'); } return $this->content; } else if (!core_tag_tag::is_enabled('core', 'post')) { $this->content = new stdClass(); $this->content->text = ''; if ($this->page->user_is_editing()) { $this->content->text = get_string('tagsaredisabled', 'tag'); } return $this->content; } else if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL and (!isloggedin() or isguestuser())) { $this->content = new stdClass(); $this->content->text = ''; return $this->content; } // require the libs and do the work require_once($CFG->dirroot .'/blog/lib.php'); if (empty($this->config)) { $this->config = new stdClass(); } if (empty($this->config->timewithin)) { $this->config->timewithin = BLOCK_BLOG_TAGS_DEFAULTTIMEWITHIN; } if (empty($this->config->numberoftags)) { $this->config->numberoftags = BLOCK_BLOG_TAGS_DEFAULTNUMBEROFTAGS; } if (empty($this->config->sort)) { $this->config->sort = BLOCK_BLOG_TAGS_DEFAULTSORT; } $this->content = new stdClass(); $this->content->text = ''; $this->content->footer = ''; /// Get a list of tags $timewithin = time() - $this->config->timewithin * 24 * 60 * 60; /// convert to seconds $context = $this->page->context; // admins should be able to read all tags $type = ''; if (!has_capability('moodle/user:readuserblogs', context_system::instance())) { $type = " AND (p.publishstate = 'site' or p.publishstate='public')"; } $sql = "SELECT t.id, t.isstandard, t.rawname, t.name, COUNT(DISTINCT ti.id) AS ct FROM {tag} t, {tag_instance} ti, {post} p, {blog_association} ba WHERE t.id = ti.tagid AND p.id = ti.itemid $type AND ti.itemtype = 'post' AND ti.component = 'core' AND ti.timemodified > $timewithin"; if ($context->contextlevel == CONTEXT_MODULE) { $sql .= " AND ba.contextid = $context->id AND p.id = ba.blogid "; } else if ($context->contextlevel == CONTEXT_COURSE) { $sql .= " AND ba.contextid = $context->id AND p.id = ba.blogid "; } $sql .= " GROUP BY t.id, t.isstandard, t.name, t.rawname ORDER BY ct DESC, t.name ASC"; if ($tags = $DB->get_records_sql($sql, null, 0, $this->config->numberoftags)) { /// There are 2 things to do: /// 1. tags with the same count should have the same size class /// 2. however many tags we have should be spread evenly over the /// 20 size classes $totaltags = count($tags); $currenttag = 0; $size = 20; $lasttagct = -1; $etags = array(); foreach ($tags as $tag) { $currenttag++; if ($currenttag == 1) { $lasttagct = $tag->ct; $size = 20; } else if ($tag->ct != $lasttagct) { $lasttagct = $tag->ct; $size = 20 - ( (int)((($currenttag - 1) / $totaltags) * 20) ); } $tag->class = ($tag->isstandard ? "standardtag " : "") . "s$size"; $etags[] = $tag; } /// Now we sort the tag display order $CFG->tagsort = $this->config->sort; usort($etags, "block_blog_tags_sort"); /// Finally we create the output /// Accessibility: markup as a list. $this->content->text .= "\n
    \n"; foreach ($etags as $tag) { $blogurl = new moodle_url('/blog/index.php'); switch ($CFG->bloglevel) { case BLOG_USER_LEVEL: $blogurl->param('userid', $USER->id); break; default: if ($context->contextlevel == CONTEXT_MODULE) { $blogurl->param('modid', $context->instanceid); } else if ($context->contextlevel == CONTEXT_COURSE) { $blogurl->param('courseid', $context->instanceid); } break; } $blogurl->param('tagid', $tag->id); $link = html_writer::link($blogurl, core_tag_tag::make_display_name($tag), array('class' => $tag->class, 'title' => get_string('numberofentries', 'blog', $tag->ct))); $this->content->text .= '
  • ' . $link . '
  • '; } $this->content->text .= "\n
\n"; } return $this->content; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = !empty($this->config) ? $this->config : new stdClass(); return (object) [ 'instance' => $configs, 'plugin' => new stdClass(), ]; } /** * This block shouldn't be added to a page if the blogs and the tags advanced features are disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { global $CFG; return $CFG->enableblogs && $CFG->usetags; } } function block_blog_tags_sort($a, $b) { global $CFG; if (empty($CFG->tagsort)) { return 0; } else { $tagsort = $CFG->tagsort; } if (is_numeric($a->$tagsort)) { return (($a->$tagsort == $b->$tagsort) ? 0 : ($a->$tagsort > $b->$tagsort)) ? 1 : -1; } elseif (is_string($a->$tagsort)) { return strcmp($a->$tagsort, $b->$tagsort); //TODO: this is not compatible with UTF-8!! } else { return 0; } } PK'9\d&blog_tags/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_blog_tags. * * @package block_blog_tags * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_blog_tags\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_blog_tags implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\Ka "blog_tags/tests/blog_tags_test.phpnu[. namespace block_blog_tags; use advanced_testcase; use block_blog_tags; use context_course; /** * PHPUnit block_blog_tags tests * * @package block_blog_tags * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_blog_tags */ final class blog_tags_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_blog_tags.php'); parent::setUpBeforeClass(); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_blog_tags(); // If blogs and tags advanced features are enabled, the method should return true. set_config('enableblogs', true); set_config('usetags', true); $this->assertTrue($block->can_block_be_added($page)); // However, if any of these advanced features is disabled, the method should return false. set_config('enableblogs', false); $this->assertFalse($block->can_block_be_added($page)); set_config('enableblogs', true); set_config('usetags', false); $this->assertFalse($block->can_block_be_added($page)); set_config('enableblogs', false); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\Q/ / %blog_tags/tests/behat/blogtag.featurenu[@block @block_blog_tag @core_blog @core_tag Feature: Adding blog tag block In order to search blog post by tag As a user I need to be able to use block blog tag @javascript Scenario: Adding block blog tag to the course Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | | Course 1 | c1 | And the following "tags" exist: | name | isstandard | | Neverusedtag | 1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | c1 | editingteacher | | student1 | c1 | student | When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Blog tags" block And the following config values are set as admin: | unaddableblocks | | theme_boost| # TODO MDL-57120 site "Blogs" link not accessible without navigation block. And I add the "Navigation" block if not present And I navigate to course participants And I click on "Course blogs" "link" in the "Navigation" "block" And I follow "Blog about this Course" And I set the following fields to these values: | Entry title | Blog post from teacher | | Blog entry body | Teacher blog post content | | Tags | Cats, dogs | And I press "Save changes" And I log out And I log in as "student1" And I am on "Course 1" course homepage And I navigate to course participants And I click on "Course blogs" "link" in the "Navigation" "block" And I follow "Blog about this Course" And I set the following fields to these values: | Entry title | Blog post from student | | Blog entry body | Student blog post content | | Tags | dogs, mice | And I press "Save changes" And I follow "c1" Then I should see "Cats" in the "Blog tags" "block" And I should see "dogs" in the "Blog tags" "block" And I should see "mice" in the "Blog tags" "block" And I click on "Cats" "link" in the "Blog tags" "block" And I should see "Blog post from teacher" And I should see "Teacher blog post content" And I log out PK'9\i2qss%recentlyaccesseditems/db/services.phpnu[. /** * Web service for Recently accessed items block * * @package block_recentlyaccesseditems * @since Moodle 3.6 * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $functions = array( 'block_recentlyaccesseditems_get_recent_items' => array( 'classname' => 'block_recentlyaccesseditems\external', 'methodname' => 'get_recent_items', 'classpath' => '', 'description' => 'List of items a user has accessed most recently.', 'type' => 'read', 'ajax' => true, 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), );PK'9\""#recentlyaccesseditems/db/access.phpnu[. /** * Capabilities for the Recently accessed items block. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/recentlyaccesseditems:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ) );PK'9\e$recentlyaccesseditems/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the recentlyaccesseditems block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @package block_recentlyaccesseditems * @copyright 2019 Peter Dias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade the recentlyaccesseditems db table. * * @param $oldversion * @return bool */ function xmldb_block_recentlyaccesseditems_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\^2%^$recentlyaccesseditems/db/install.xmlnu[
PK'9\x#recentlyaccesseditems/db/events.phpnu[. /** * Recently accessed items event observer. * * @package block_recentlyaccesseditems * @category event * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $observers = array ( array( 'eventname' => '\core\event\course_module_viewed', 'callback' => 'block_recentlyaccesseditems\observer::store', ), array( 'eventname' => '\core\event\course_module_deleted', 'callback' => 'block_recentlyaccesseditems\observer::remove' ), );PK'9\>=recentlyaccesseditems/lang/en/block_recentlyaccesseditems.phpnu[. /** * Strings for the Recently accessed items block. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['moreitems'] = 'Show more items'; $string['noitems'] = 'No recent items'; $string['pluginname'] = 'Recently accessed items'; $string['privacy:metadata:cmid'] = 'The ID of the activity or resource'; $string['privacy:metadata:courseid'] = 'Course the item belongs to'; $string['privacy:metadata:block_recentlyaccesseditemstablesummary'] = 'The Recently accessed items block stores information about items that the user accessed recently'; $string['privacy:metadata:timeaccess'] = 'The time when the user last accessed the item'; $string['privacy:metadata:userid'] = 'The ID of the user who accessed the item'; $string['privacy:recentlyaccesseditemspath'] = 'Recently accessed items'; $string['recentlyaccesseditems:myaddinstance'] = 'Add a new recently accessed items block to Dashboard';PK'9\#recentlyaccesseditems/pix/items.svgnu[ PK'9\4,FFrecentlyaccesseditems/lib.phpnu[. /** * The interface library between the core and the subsystem. * * @package block_recentlyaccesseditems * @copyright 2019 Peter Dias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Pre-delete course module hook to cleanup any records with references to the deleted module. * * @param stdClass $cm The deleted course module */ function block_recentlyaccesseditems_pre_course_module_delete($cm) { global $DB; $DB->delete_records('block_recentlyaccesseditems', ['cmid' => $cm->id]); } /** * Pre-delete course hook to cleanup any records with references to the deleted course. * * @param stdClass $course The deleted course */ function block_recentlyaccesseditems_pre_course_delete($course) { global $DB; $DB->delete_records('block_recentlyaccesseditems', ['courseid' => $course->id]); } PK'9\d!recentlyaccesseditems/version.phpnu[. /** * Version details for the Recent activities block. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_recentlyaccesseditems'; // Full name of the plugin (used for diagnostics). PK'9\ *recentlyaccesseditems/classes/external.phpnu[. namespace block_recentlyaccesseditems; use block_recentlyaccesseditems\external\recentlyaccesseditems_item_exporter; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_value; use context_user; use context_module; /** * External API class. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class external extends external_api { /** * Returns description of method parameters * @return external_function_parameters */ public static function get_recent_items_parameters() { return new external_function_parameters( array( 'limit' => new external_value(PARAM_INT, 'result set limit', VALUE_DEFAULT, 0) ) ); } /** * Get last accessed items by the logged user (activities or resources). * * @param int $limit Max num of items to return * @return array List of items * @since Moodle 3.6 */ public static function get_recent_items(int $limit = 0) { global $USER, $PAGE; $userid = $USER->id; $params = self::validate_parameters(self::get_recent_items_parameters(), array( 'limit' => $limit, ) ); $limit = $params['limit']; self::validate_context(context_user::instance($userid)); $items = helper::get_recent_items($limit); $renderer = $PAGE->get_renderer('core'); $recentitems = array_map(function($item) use ($renderer) { $context = context_module::instance($item->cmid); $exporter = new recentlyaccesseditems_item_exporter($item, ['context' => $context]); return $exporter->export($renderer); }, $items); return $recentitems; } /** * Returns description of method result value * * @return \core_external\external_description * @since Moodle 3.6 */ public static function get_recent_items_returns() { return new external_multiple_structure(recentlyaccesseditems_item_exporter::get_read_structure(), 'The most recently accessed activities/resources by the logged user'); } } PK'9\ \wwNrecentlyaccesseditems/classes/external/recentlyaccesseditems_item_exporter.phpnu[. /** * Class for exporting the data needed to render a recent accessed item. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recentlyaccesseditems\external; use core_course\output\activity_icon; use renderer_base; use moodle_url; /** * Class for exporting the data needed to render a recent accessed item. * * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class recentlyaccesseditems_item_exporter extends \core\external\exporter { /** * Returns a list of objects that are related to this persistent. * */ protected static function define_related() { // We cache the context so it does not need to be retrieved from the course. return array('context' => '\\context'); } /** * Get the additional values to inject while exporting. * * @param renderer_base $output The renderer * @return array Additional properties with values */ protected function get_other_values(renderer_base $output) { global $CFG; require_once($CFG->libdir.'/modinfolib.php'); $renderer = \core\di::get(\core\output\renderer_helper::class)->get_core_renderer(); $cminfo = get_fast_modinfo($this->data->courseid)->get_cm($this->data->cmid); $icon = activity_icon::from_cm_info($cminfo); return array( 'viewurl' => (new moodle_url('/mod/'.$this->data->modname.'/view.php', array('id' => $this->data->cmid)))->out(false), 'courseviewurl' => (new moodle_url('/course/view.php', array('id' => $this->data->courseid)))->out(false), 'icon' => $renderer->render($icon), 'purpose' => plugin_supports('mod', $this->data->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER), 'branded' => $icon->is_branded(), ); } /** * Return the list of properties. * * @return array Properties. */ public static function define_properties() { return array( 'id' => array( 'type' => PARAM_INT, ), 'courseid' => array( 'type' => PARAM_INT, ), 'cmid' => array( 'type' => PARAM_INT, ), 'userid' => array( 'type' => PARAM_INT, ), 'modname' => array( 'type' => PARAM_PLUGIN, ), 'name' => array( 'type' => PARAM_TEXT, ), 'coursename' => array( 'type' => PARAM_TEXT, ), 'timeaccess' => array( 'type' => PARAM_INT, ) ); } /** * Return the list of additional properties. * * @return array Additional properties. */ public static function define_other_properties() { return array( 'viewurl' => array( 'type' => PARAM_RAW, ), 'courseviewurl' => array( 'type' => PARAM_URL, ), 'icon' => array( 'type' => PARAM_RAW, ), 'purpose' => array( 'type' => PARAM_ALPHA, ), 'branded' => [ 'type' => PARAM_BOOL, 'optional' => true, ], ); } } PK'9\"d2recentlyaccesseditems/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for Recently accessed items block. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recentlyaccesseditems\privacy; defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; use \core_privacy\local\request\transform; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\userlist; use \core_privacy\local\request\approved_contextlist; use \core_privacy\local\request\approved_userlist; use \core_privacy\local\request\writer; /** * Privacy Subsystem for block_recentlyaccesseditems. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns information about the user data stored in this component. * * @param collection $collection A list of information about this component * @return collection The collection object filled out with information about this component. */ public static function get_metadata(collection $collection): collection { $recentitems = [ 'userid' => 'privacy:metadata:userid', 'courseid' => 'privacy:metadata:courseid', 'cmid' => 'privacy:metadata:cmid', 'timeaccess' => 'privacy:metadata:timeaccess' ]; $collection->add_database_table('block_recentlyaccesseditems', $recentitems, 'privacy:metadata:block_recentlyaccesseditemstablesummary'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { $params = ['userid' => $userid, 'contextuser' => CONTEXT_USER]; $sql = "SELECT c.id FROM {context} c JOIN {block_recentlyaccesseditems} b ON b.userid = c.instanceid WHERE c.instanceid = :userid AND c.contextlevel = :contextuser"; $contextlist = new contextlist(); $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users within a specific context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { global $DB; $context = $userlist->get_context(); if (!$context instanceof \context_user) { return; } if ($DB->record_exists('block_recentlyaccesseditems', ['userid' => $context->instanceid])) { $userlist->add_user($context->instanceid); } } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { $context = $contextlist->current(); $user = \core_user::get_user($contextlist->get_user()->id); static::export_recentitems($user->id, $context); } /** * Export information about the most recently accessed items. * * @param int $userid The user ID. * @param \context $context The user context. */ protected static function export_recentitems(int $userid, \context $context) { global $DB; $sql = "SELECT ra.id, c.fullname, ra.timeaccess, m.name, ra.cmid FROM {block_recentlyaccesseditems} ra JOIN {course} c ON c.id = ra.courseid JOIN {course_modules} cm on cm.id = ra.cmid JOIN {modules} m ON m.id = cm.module WHERE ra.userid = :userid"; $params = ['userid' => $userid]; $records = $DB->get_records_sql($sql, $params); if (!empty($records)) { $recentitems = (object) array_map(function($record) use($context) { return [ 'course_name' => format_string($record->fullname, true, ['context' => $context]), 'module_name' => format_string($record->name), 'timeaccess' => transform::datetime($record->timeaccess) ]; }, $records); writer::with_context($context)->export_data([get_string('privacy:recentlyaccesseditemspath', 'block_recentlyaccesseditems')], $recentitems); } } /** * Delete all data for all users in the specified context. * * @param context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(\context $context) { global $DB; // Only delete data for a user context. if ($context->contextlevel == CONTEXT_USER) { // Delete recent items access. $DB->delete_records('block_recentlyaccesseditems', ['userid' => $context->instanceid]); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { global $DB; foreach ($contextlist as $context) { // Let's be super certain that we have the right information for this user here. if ($context->contextlevel == CONTEXT_USER && $contextlist->get_user()->id == $context->instanceid) { $DB->delete_records('block_recentlyaccesseditems', ['userid' => $context->instanceid]); } } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { global $DB; $context = $userlist->get_context(); if ($context instanceof \context_user && in_array($context->instanceid, $userlist->get_userids())) { $DB->delete_records('block_recentlyaccesseditems', ['userid' => $context->instanceid]); } } } PK'9\\2 *recentlyaccesseditems/classes/observer.phpnu[. /** * Event observer. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recentlyaccesseditems; defined('MOODLE_INTERNAL') || die(); /** * Events observer. * * Stores all actions about modules viewed in block_recentlyaccesseditems table. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class observer { /** * @var string Block table name. */ private static $table = 'block_recentlyaccesseditems'; /** * Register items views in block_recentlyaccesseditems table. * * When the item is view for the first time, a new record is created. If the item was viewed before, the time is * updated. * * @param \core\event\base $event */ public static function store(\core\event\base $event) { global $DB; if (!isloggedin() or \core\session\manager::is_loggedinas() or isguestuser()) { // No access tracking. return; } $conditions = [ 'userid' => $event->userid ]; $records = $DB->get_records(self::$table, $conditions, "timeaccess DESC"); foreach ($records as $record) { if (($record->userid == $event->userid) && ($record->cmid == $event->contextinstanceid)) { $conditions = [ 'userid' => $event->userid, 'cmid' => $event->contextinstanceid ]; $DB->set_field(self::$table, 'timeaccess', $event->timecreated, $conditions); return; } } if (count($records) >= 9) { $conditions = [ 'id' => end($records)->id, ]; $DB->delete_records(self::$table, $conditions); } $eventdata = new \stdClass(); $eventdata->cmid = $event->contextinstanceid; $eventdata->timeaccess = $event->timecreated; $eventdata->courseid = $event->courseid; $eventdata->userid = $event->userid; $DB->insert_record(self::$table, $eventdata); } /** * Remove record when course module is deleted. * * @param \core\event\base $event */ public static function remove(\core\event\base $event) { global $DB; $DB->delete_records(self::$table, array('cmid' => $event->contextinstanceid)); } }PK'9\  (recentlyaccesseditems/classes/helper.phpnu[. /** * Recently accessed items helper. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recentlyaccesseditems; defined('MOODLE_INTERNAL') || die(); /** * Recently accessed items helper. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class helper { /** * Returns a list of the most recently items accessed by the logged user * * @param int $limit Restrict result set to this amount * @return array List of recent items accessed by userid */ public static function get_recent_items(int $limit = 0) { global $USER, $DB; $userid = $USER->id; $courses = array(); $recentitems = array(); if (!isloggedin() or \core\session\manager::is_loggedinas() or isguestuser()) { // No access tracking. return $recentitems; } $paramsql = array('userid' => $userid); $sql = "SELECT rai.* FROM {block_recentlyaccesseditems} rai JOIN {course} c ON c.id = rai.courseid WHERE userid = :userid ORDER BY rai.timeaccess DESC, rai.id DESC"; $records = $DB->get_records_sql($sql, $paramsql); $order = 0; // Get array of items by course. Use $order index to keep sql sorted results. foreach ($records as $record) { $courses[$record->courseid][$order++] = $record; } // Group by courses to reduce get_fast_modinfo requests. foreach ($courses as $key => $items) { $modinfo = get_fast_modinfo($key); if (!can_access_course($modinfo->get_course(), null, '', true)) { continue; } foreach ($items as $key => $item) { // Exclude not visible items. if (!$modinfo->cms[$item->cmid]->uservisible) { continue; } $item->modname = $modinfo->cms[$item->cmid]->modname; $item->name = $modinfo->cms[$item->cmid]->name; $item->coursename = get_course_display_name_for_list($modinfo->get_course()); $recentitems[$key] = $item; } } ksort($recentitems); // Apply limit. if (!$limit) { $limit = count($recentitems); } $recentitems = array_slice($recentitems, 0, $limit); return $recentitems; } }PK'9\i"-recentlyaccesseditems/classes/output/main.phpnu[. /** * Class containing data for Recently accessed items block. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recentlyaccesseditems\output; defined('MOODLE_INTERNAL') || die(); use renderable; use renderer_base; use templatable; /** * Class containing data for Recently accessed items block. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class main implements renderable, templatable { /** * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output * @return array */ public function export_for_template(renderer_base $output) { $noitemsimgurl = $output->image_url('items', 'block_recentlyaccesseditems')->out(); return [ 'noitemsimgurl' => $noitemsimgurl ]; } }PK'9\|2oC1recentlyaccesseditems/classes/output/renderer.phpnu[. /** * Recently accessed items block renderer * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recentlyaccesseditems\output; defined('MOODLE_INTERNAL') || die; use plugin_renderer_base; /** * Recently accessed items block renderer * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends plugin_renderer_base { /** * Return the main content for the Recently accessed items block. * * @param \renderer_base $main The main renderable * @return string HTML string */ public function render_recentlyaccesseditems(renderer_base $main) { return $this->render_from_template('block_recentlyaccesseditems/main', $main->export_for_template($this)); } }PK'9\[J77-recentlyaccesseditems/templates/main.mustachenu[{{! 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 . }} {{! @template block_recentlyaccesseditems/main This template renders the main content area for the Recently accessed itemes block. Example context (json): {} }}
{{> block_recentlyaccesseditems/recentlyaccesseditems-view }}
{{#js}} require( [ 'jquery', 'block_recentlyaccesseditems/main', ], function( $, Main ) { var root = $('#block-recentlyaccesseditems-{{uniqid}}'); Main.init(root); }); {{/js}}PK'9\*1recentlyaccesseditems/templates/no-items.mustachenu[{{! 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 . }} {{! @template block_recentlyaccesseditems/no-items This template renders the no items message. Example context (json): { "noitemsimgurl": "https://moodlesite/theme/image.php/boost/block_recentlyaccesseditems/1535727318/items" } }}

{{#str}} noitems, block_recentlyaccesseditems {{/str}}

PK'9\lo``9recentlyaccesseditems/templates/placeholder-item.mustachenu[{{! 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 . }} {{! @template block_recentlyaccesseditems/placeholder-item This template renders a card item loading placeholder for the Recently accessed items block. Example context (json): {} }}
PK'9\{ 3recentlyaccesseditems/templates/view-cards.mustachenu[{{! 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 Licensebllsdsadfasfd along with Moodle. If not, see . }} {{! @template block_recentlyaccesseditems/view-cards This template renders the items cards of the Recently accessed items block. Example context (json): { "items": [ { "cmid": 64, "courseid": 2, "coursename": "Course", "courseviewurl": "https://moodlesite/course/view.php?id=2", "icon": "\"Forum\"", "name": "Assignment due 1", "id": 17, "modname": "Forum", "name": "Forum", "timeaccess": 1539848498, "userid": 2, "viewurl": "http://moodlesite/mod/forum?id=64", "purpose": "assessment" } ] } }} {{#hasmoreitems}}
{{/hasmoreitems}} PK'9\. }} {{! @template block_recentlyaccesseditems/recentlyaccesseditems-view This template renders the items view for the Recently accessed items block. Example context (json): { "noitemsimgurl": "https://moodlesite/theme/image.php/boost/block_recentactivities/1535727318/items" } }}
{{> block_recentlyaccesseditems/placeholder-item }} {{> block_recentlyaccesseditems/placeholder-item }} {{> block_recentlyaccesseditems/placeholder-item }} {{> block_recentlyaccesseditems/placeholder-item }} {{> block_recentlyaccesseditems/placeholder-item }}
PK'9\|~!recentlyaccesseditems/upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This file describes API changes in the recentlyaccesseditems block code. === 4.4 === * The external function \block_recentlyaccesseditems\external\get_recent_items() now returns a field called "branded" indicating whether the module is branded or not. === 3.7 === * The 'block/recentlyaccesseditems:addinstance' capability has been removed. It has never been used in code.PK'9\w_\::+recentlyaccesseditems/amd/src/repository.jsnu[// 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 . /** * A javascript module to handle user ajax actions. * * @module block_recentlyaccesseditems/repository * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['core/ajax'], function(Ajax) { /** * Get the list of items that the user has most recently accessed. * * @method getRecentItems * @param {int} limit Only return this many results * @return {promise} Resolved with an array of items */ var getRecentItems = function(limit) { var args = {}; if (typeof limit !== 'undefined') { args.limit = limit; } var request = { methodname: 'block_recentlyaccesseditems_get_recent_items', args: args }; return Ajax.call([request])[0]; }; return { getRecentItems: getRecentItems }; });PK'9\[V%recentlyaccesseditems/amd/src/main.jsnu[ // 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 . /** * Javascript to initialise the Recently accessed items block. * * @module block_recentlyaccesseditems/main * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'block_recentlyaccesseditems/repository', 'core/templates', 'core/notification' ], function( $, Repository, Templates, Notification ) { var NUM_ITEMS = 9; // Maximum number of elements to display in the block initially. var NUM_ITEMS_INIT = 3; var SELECTORS = { CARDDECK_CONTAINER: '[data-region="recentlyaccesseditems-view"]', CARDDECK: '[data-region="recentlyaccesseditems-view-content"]', SHOWMORE_LINK: '[data-region="recentlyaccesseditems-view"] [data-action="more-items"]', }; /** * Register event listeners. */ const registerEventListeners = () => { const showmoreLink = document.querySelector(SELECTORS.SHOWMORE_LINK); // Hide "Show more" link and show additional items. showmoreLink.addEventListener('click', () => { showmoreLink.classList.add('d-none'); const hiddenItems = document.querySelector('[data-region="items-list"]').children; for (const hiddenItem of hiddenItems) { hiddenItem.style = "display: block"; } }); }; /** * Get recent items from backend. * * @method getRecentItems * @param {int} limit Only return this many results * @return {array} Items user most recently has accessed */ var getRecentItems = function(limit) { return Repository.getRecentItems(limit); }; /** * Render the block content. * * @method renderItems * @param {object} root The root element for the items view. * @param {array} items containing array of returned items. * @return {promise} Resolved with HTML and JS strings */ var renderItems = function(root, items) { if (items.length > 0) { let hasmoreitems = false; if (items.length > NUM_ITEMS_INIT) { hasmoreitems = true; } return Templates.render('block_recentlyaccesseditems/view-cards', { items: items, hasmoreitems: hasmoreitems }); } else { var noitemsimgurl = root.attr('data-noitemsimgurl'); return Templates.render('block_recentlyaccesseditems/no-items', { noitemsimgurl: noitemsimgurl }); } }; /** * Get and show the recent items into the block. * * @param {object} root The root element for the items block. */ var init = function(root) { root = $(root); var itemsContainer = root.find(SELECTORS.CARDDECK_CONTAINER); var itemsContent = root.find(SELECTORS.CARDDECK); var itemsPromise = getRecentItems(NUM_ITEMS); itemsPromise.then(function(items) { var pageContentPromise = renderItems(itemsContainer, items); pageContentPromise.then(function(html, js) { Templates.replaceNodeContents(itemsContent, html, js); if (items.length > 3) { registerEventListeners(); } return null; }).catch(Notification.exception); }).catch(Notification.exception); }; return { init: init }; });PK'9\m`771recentlyaccesseditems/amd/build/repository.min.jsnu[/** * A javascript module to handle user ajax actions. * * @module block_recentlyaccesseditems/repository * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_recentlyaccesseditems/repository",["core/ajax"],(function(Ajax){return{getRecentItems:function(limit){var args={};void 0!==limit&&(args.limit=limit);var request={methodname:"block_recentlyaccesseditems_get_recent_items",args:args};return Ajax.call([request])[0]}}})); //# sourceMappingURL=repository.min.js.mapPK'9\q5recentlyaccesseditems/amd/build/repository.min.js.mapnu[{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to handle user ajax actions.\n *\n * @module block_recentlyaccesseditems/repository\n * @copyright 2018 Victor Deniz \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['core/ajax'], function(Ajax) {\n\n /**\n * Get the list of items that the user has most recently accessed.\n *\n * @method getRecentItems\n * @param {int} limit Only return this many results\n * @return {promise} Resolved with an array of items\n */\n var getRecentItems = function(limit) {\n var args = {};\n if (typeof limit !== 'undefined') {\n args.limit = limit;\n }\n var request = {\n methodname: 'block_recentlyaccesseditems_get_recent_items',\n args: args\n };\n return Ajax.call([request])[0];\n };\n return {\n getRecentItems: getRecentItems\n };\n});"],"names":["define","Ajax","getRecentItems","limit","args","request","methodname","call"],"mappings":";;;;;;;AAsBAA,gDAAO,CAAC,cAAc,SAASC,YAoBpB,CACHC,eAZiB,SAASC,WACtBC,KAAO,QACU,IAAVD,QACPC,KAAKD,MAAQA,WAEbE,QAAU,CACVC,WAAY,+CACZF,KAAMA,aAEHH,KAAKM,KAAK,CAACF,UAAU"}PK'9\1٢/recentlyaccesseditems/amd/build/main.min.js.mapnu[{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["\n// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to initialise the Recently accessed items block.\n *\n * @module block_recentlyaccesseditems/main\n * @copyright 2018 Victor Deniz \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n [\n 'jquery',\n 'block_recentlyaccesseditems/repository',\n 'core/templates',\n 'core/notification'\n ],\n function(\n $,\n Repository,\n Templates,\n Notification\n ) {\n\n var NUM_ITEMS = 9;\n // Maximum number of elements to display in the block initially.\n var NUM_ITEMS_INIT = 3;\n\n var SELECTORS = {\n CARDDECK_CONTAINER: '[data-region=\"recentlyaccesseditems-view\"]',\n CARDDECK: '[data-region=\"recentlyaccesseditems-view-content\"]',\n SHOWMORE_LINK: '[data-region=\"recentlyaccesseditems-view\"] [data-action=\"more-items\"]',\n };\n\n /**\n * Register event listeners.\n */\n const registerEventListeners = () => {\n const showmoreLink = document.querySelector(SELECTORS.SHOWMORE_LINK);\n\n // Hide \"Show more\" link and show additional items.\n showmoreLink.addEventListener('click', () => {\n showmoreLink.classList.add('d-none');\n\n const hiddenItems = document.querySelector('[data-region=\"items-list\"]').children;\n for (const hiddenItem of hiddenItems) {\n hiddenItem.style = \"display: block\";\n }\n });\n };\n\n /**\n * Get recent items from backend.\n *\n * @method getRecentItems\n * @param {int} limit Only return this many results\n * @return {array} Items user most recently has accessed\n */\n var getRecentItems = function(limit) {\n return Repository.getRecentItems(limit);\n };\n\n /**\n * Render the block content.\n *\n * @method renderItems\n * @param {object} root The root element for the items view.\n * @param {array} items containing array of returned items.\n * @return {promise} Resolved with HTML and JS strings\n */\n var renderItems = function(root, items) {\n if (items.length > 0) {\n let hasmoreitems = false;\n if (items.length > NUM_ITEMS_INIT) {\n hasmoreitems = true;\n }\n return Templates.render('block_recentlyaccesseditems/view-cards', {\n items: items,\n hasmoreitems: hasmoreitems\n });\n } else {\n var noitemsimgurl = root.attr('data-noitemsimgurl');\n return Templates.render('block_recentlyaccesseditems/no-items', {\n noitemsimgurl: noitemsimgurl\n });\n }\n };\n\n /**\n * Get and show the recent items into the block.\n *\n * @param {object} root The root element for the items block.\n */\n var init = function(root) {\n root = $(root);\n\n var itemsContainer = root.find(SELECTORS.CARDDECK_CONTAINER);\n var itemsContent = root.find(SELECTORS.CARDDECK);\n\n var itemsPromise = getRecentItems(NUM_ITEMS);\n\n itemsPromise.then(function(items) {\n var pageContentPromise = renderItems(itemsContainer, items);\n\n pageContentPromise.then(function(html, js) {\n Templates.replaceNodeContents(itemsContent, html, js);\n if (items.length > 3) {\n registerEventListeners();\n }\n return null;\n }).catch(Notification.exception);\n }).catch(Notification.exception);\n };\n\n return {\n init: init\n };\n });"],"names":["define","$","Repository","Templates","Notification","SELECTORS","init","root","limit","itemsContainer","find","itemsContent","getRecentItems","then","items","pageContentPromise","length","hasmoreitems","render","noitemsimgurl","attr","renderItems","html","js","replaceNodeContents","showmoreLink","document","querySelector","addEventListener","classList","add","hiddenItems","children","hiddenItem","style","registerEventListeners","catch","exception"],"mappings":";;;;;;;AAwBAA,0CACI,CACI,SACA,yCACA,iBACA,sBAEJ,SACIC,EACAC,WACAC,UACAC,kBAOIC,6BACoB,6CADpBA,mBAEU,qDAFVA,wBAGe,8EAmFZ,CACHC,KAtBO,SAASC,UAnCUC,MAsCtBC,gBAFJF,KAAON,EAAEM,OAEiBG,KAAKL,8BAC3BM,aAAeJ,KAAKG,KAAKL,qBAvCHG,MAlCd,EAmCLN,WAAWU,eAAeJ,QA0CpBK,MAAK,SAASC,WACnBC,mBAhCM,SAASR,KAAMO,UACzBA,MAAME,OAAS,EAAG,KACdC,cAAe,SACfH,MAAME,OA/CG,IAgDTC,cAAe,GAEZd,UAAUe,OAAO,yCAA0C,CAC9DJ,MAAOA,MACPG,aAAcA,mBAGdE,cAAgBZ,KAAKa,KAAK,6BACvBjB,UAAUe,OAAO,uCAAwC,CAC5DC,cAAeA,gBAmBME,CAAYZ,eAAgBK,OAErDC,mBAAmBF,MAAK,SAASS,KAAMC,WACnCpB,UAAUqB,oBAAoBb,aAAcW,KAAMC,IAC9CT,MAAME,OAAS,GArEA,YACrBS,aAAeC,SAASC,cAActB,yBAG5CoB,aAAaG,iBAAiB,SAAS,KACnCH,aAAaI,UAAUC,IAAI,gBAErBC,YAAcL,SAASC,cAAc,8BAA8BK,aACpE,MAAMC,cAAcF,YACrBE,WAAWC,MAAQ,qBA6DfC,GAEG,QACRC,MAAMhC,aAAaiC,cACvBD,MAAMhC,aAAaiC"}PK'9\'+recentlyaccesseditems/amd/build/main.min.jsnu[/** * Javascript to initialise the Recently accessed items block. * * @module block_recentlyaccesseditems/main * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_recentlyaccesseditems/main",["jquery","block_recentlyaccesseditems/repository","core/templates","core/notification"],(function($,Repository,Templates,Notification){var SELECTORS_CARDDECK_CONTAINER='[data-region="recentlyaccesseditems-view"]',SELECTORS_CARDDECK='[data-region="recentlyaccesseditems-view-content"]',SELECTORS_SHOWMORE_LINK='[data-region="recentlyaccesseditems-view"] [data-action="more-items"]';return{init:function(root){var limit,itemsContainer=(root=$(root)).find(SELECTORS_CARDDECK_CONTAINER),itemsContent=root.find(SELECTORS_CARDDECK);(limit=9,Repository.getRecentItems(limit)).then((function(items){var pageContentPromise=function(root,items){if(items.length>0){let hasmoreitems=!1;return items.length>3&&(hasmoreitems=!0),Templates.render("block_recentlyaccesseditems/view-cards",{items:items,hasmoreitems:hasmoreitems})}var noitemsimgurl=root.attr("data-noitemsimgurl");return Templates.render("block_recentlyaccesseditems/no-items",{noitemsimgurl:noitemsimgurl})}(itemsContainer,items);pageContentPromise.then((function(html,js){return Templates.replaceNodeContents(itemsContent,html,js),items.length>3&&(()=>{const showmoreLink=document.querySelector(SELECTORS_SHOWMORE_LINK);showmoreLink.addEventListener("click",(()=>{showmoreLink.classList.add("d-none");const hiddenItems=document.querySelector('[data-region="items-list"]').children;for(const hiddenItem of hiddenItems)hiddenItem.style="display: block"}))})(),null})).catch(Notification.exception)})).catch(Notification.exception)}}})); //# sourceMappingURL=main.min.js.mapPK'9\/-recentlyaccesseditems/tests/observer_test.phpnu[. namespace block_recentlyaccesseditems; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/mod/assign/tests/generator.php'); /** * Block Recently accessed items observer tests. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.6 */ final class observer_test extends \advanced_testcase { use \mod_assign_test_generator; /** @var string Table name. */ protected $table; /** @var \stdClass course data. */ protected $course; /** @var \stdClass student data. */ protected $student; /** @var \stdClass teacher data. */ protected $teacher; /** @var \stdClass student role. */ protected $studentrole; /** @var \stdClass teacher role. */ protected $teacherrole; /** @var \stdClass course forum. */ protected $forum; /** @var \stdClass course glossary. */ protected $glossary; /** @var \stdClass course assignment. */ protected $assign; /** * Set up for every test */ public function setUp(): void { global $DB; parent::setUp(); $this->resetAfterTest(); $this->setAdminUser(); // Block table name. $this->table = "block_recentlyaccesseditems"; // Setup test data. $this->course = $this->getDataGenerator()->create_course(); // Create users. $this->student = self::getDataGenerator()->create_user(); $this->teacher = self::getDataGenerator()->create_user(); // Users enrolments. $this->studentrole = $DB->get_record('role', array('shortname' => 'student')); $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual'); $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual'); // Create items. $this->forum = $this->getDataGenerator()->create_module('forum', array('course' => $this->course)); $this->glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $this->course)); $this->assign = $this->getDataGenerator()->create_module('assign', ['course' => $this->course]); } /** * Test items views are recorded * * When items events are triggered they are stored in the block_recentlyaccesseditems table. */ public function test_item_view_recorded_testcase(): void { global $DB; // Empty table at the beggining. $records = $DB->count_records($this->table, array()); $this->assertEquals(0, $records); // Teacher access forum activity. $this->setUser($this->teacher); $event = \mod_forum\event\course_module_viewed::create(array('context' => \context_module::instance($this->forum->cmid), 'objectid' => $this->forum->id)); $event->trigger(); // Student access assignment activity. $this->setUser($this->student); $event1 = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($this->assign->cmid), 'objectid' => $this->assign->id]); $event1->trigger(); $records = $DB->count_records($this->table, array('userid' => $this->teacher->id, 'courseid' => $this->course->id, 'cmid' => $this->forum->cmid)); $this->assertEquals(1, $records); $records = $DB->count_records($this->table, ['userid' => $this->student->id, 'courseid' => $this->course->id, 'cmid' => $this->assign->cmid]); $this->assertEquals(1, $records); $this->waitForSecond(); // Student access assignment activity again after 1 second (no new record created, timeaccess updated). $event2 = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($this->assign->cmid), 'objectid' => $this->assign->id]); $event2->trigger(); $records = $DB->get_records($this->table, ['userid' => $this->student->id, 'courseid' => $this->course->id, 'cmid' => $this->assign->cmid]); $this->assertCount(1, $records); $this->assertEquals($event2->timecreated, array_shift($records)->timeaccess); } /** * Test removed items records are deleted. * * When a course module is removed, the records associated in the block_recentlyaccesseditems table are deleted. */ public function test_item_delete_record_testcase(): void { global $DB; // Empty table at the beggining. $records = $DB->count_records($this->table, array()); $this->assertEquals(0, $records); // Teacher access forum activity. $this->setUser($this->teacher); $event = \mod_forum\event\course_module_viewed::create(array('context' => \context_module::instance($this->forum->cmid), 'objectid' => $this->forum->id)); $event->trigger(); // Teacher access assignment activity. $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($this->assign->cmid), 'objectid' => $this->assign->id]); $event->trigger(); // Student access assignment activity. $this->setUser($this->student); $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($this->assign->cmid), 'objectid' => $this->assign->id]); $event->trigger(); // Student access forum activity. $event = \mod_forum\event\course_module_viewed::create(array('context' => \context_module::instance($this->forum->cmid), 'objectid' => $this->forum->id)); $event->trigger(); $records = $DB->count_records($this->table, array('cmid' => $this->forum->cmid)); $this->assertEquals(2, $records); course_delete_module($this->forum->cmid); $records = $DB->count_records($this->table, array('cmid' => $this->forum->cmid)); $this->assertEquals(0, $records); $records = $DB->count_records($this->table, ['cmid' => $this->assign->cmid]); $this->assertEquals(2, $records); } } PK'9\ +recentlyaccesseditems/tests/helper_test.phpnu[. namespace block_recentlyaccesseditems; /** * Block Recently accessed helper class tests. * * @package block_recentlyaccesseditems * @copyright 2019 University of Nottingham * @author Neill Magill * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class helper_test extends \advanced_testcase { /** * Tests that the get recent items method can handle getting records when courses have been deleted. */ public function test_get_recent_items(): void { $this->resetAfterTest(); $course = self::getDataGenerator()->create_course(); $coursetodelete = self::getDataGenerator()->create_course(); $user = self::getDataGenerator()->create_and_enrol($course, 'student'); self::getDataGenerator()->enrol_user($user->id, $coursetodelete->id, 'student'); // Add an activity to each course. $forum = self::getDataGenerator()->create_module('forum', ['course' => $course]); $glossary = self::getDataGenerator()->create_module('glossary', ['course' => $coursetodelete]); self::setUser($user); // Get the user to visit the activities. $event1params = ['context' => \context_module::instance($forum->cmid), 'objectid' => $forum->id]; $event1 = \mod_forum\event\course_module_viewed::create($event1params); $event1->trigger(); $event2params = ['context' => \context_module::instance($glossary->cmid), 'objectid' => $glossary->id]; $event2 = \mod_glossary\event\course_module_viewed::create($event2params); $event2->trigger(); $recent1 = helper::get_recent_items(); self::assertCount(2, $recent1); $recentlimited = helper::get_recent_items(1); self::assertCount(1, $recentlimited); delete_course($coursetodelete, false); // There should be no errors if a course has been deleted. $recent2 = helper::get_recent_items(); self::assertCount(1, $recent2); } } PK'9\0zRzR5recentlyaccesseditems/tests/privacy/provider_test.phpnu[. /** * Block recentlyaccesseditems privacy provider tests. * * @package block_recentlyaccesseditems * @copyright 2018 Michael Hawkins * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.6 */ namespace block_recentlyaccesseditems\privacy; defined('MOODLE_INTERNAL') || die(); use block_recentlyaccesseditems\privacy\provider; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\approved_userlist; /** * Block Recently accessed items privacy provider tests. * * @package block_recentlyaccesseditems * @copyright 2018 Michael Hawkins * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.6 */ final class provider_test extends \core_privacy\tests\provider_testcase { /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid(): void { $this->resetAfterTest(); $generator = $this->getDataGenerator(); $student = $generator->create_user(); $studentcontext = \context_user::instance($student->id); $teacher = $generator->create_user(); $teachercontext = \context_user::instance($teacher->id); // Enrol users in course and add course items. $course = $generator->create_course(); $generator->enrol_user($student->id, $course->id, 'student'); $generator->enrol_user($teacher->id, $course->id, 'teacher'); $forum = $generator->create_module('forum', ['course' => $course]); $assign = $generator->create_module('assign', ['course' => $course]); // Check nothing is found before block is populated. $contextlist1 = provider::get_contexts_for_userid($student->id); $this->assertCount(0, $contextlist1); $contextlist2 = provider::get_contexts_for_userid($teacher->id); $this->assertCount(0, $contextlist2); // Generate some recent activity for both users. $this->setUser($student); $event = \mod_forum\event\course_module_viewed::create(['context' => \context_module::instance($forum->cmid), 'objectid' => $forum->id]); $event->trigger(); $this->setUser($teacher); $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($assign->cmid), 'objectid' => $assign->id]); $event->trigger(); // Ensure provider only fetches the users's own context. $contextlist1 = provider::get_contexts_for_userid($student->id); $this->assertCount(1, $contextlist1); $this->assertEquals($studentcontext, $contextlist1->current()); $contextlist2 = provider::get_contexts_for_userid($teacher->id); $this->assertCount(1, $contextlist2); $this->assertEquals($teachercontext, $contextlist2->current()); } /** * Test getting users in the context ID related to this plugin. */ public function test_get_users_in_context(): void { $this->resetAfterTest(); $generator = $this->getDataGenerator(); $component = 'block_recentlyaccesseditems'; $student = $generator->create_user(); $studentcontext = \context_user::instance($student->id); $teacher = $generator->create_user(); $teachercontext = \context_user::instance($teacher->id); // Enrol users in course and add course items. $course = $generator->create_course(); $generator->enrol_user($student->id, $course->id, 'student'); $generator->enrol_user($teacher->id, $course->id, 'teacher'); $forum = $generator->create_module('forum', ['course' => $course]); $assign = $generator->create_module('assign', ['course' => $course]); // Check nothing is found before block is populated. $userlist1 = new \core_privacy\local\request\userlist($studentcontext, $component); provider::get_users_in_context($userlist1); $this->assertCount(0, $userlist1); $userlist2 = new \core_privacy\local\request\userlist($teachercontext, $component); provider::get_users_in_context($userlist2); $this->assertCount(0, $userlist2); // Generate some recent activity for both users. $this->setUser($student); $event = \mod_forum\event\course_module_viewed::create(['context' => \context_module::instance($forum->cmid), 'objectid' => $forum->id]); $event->trigger(); $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($assign->cmid), 'objectid' => $assign->id]); $event->trigger(); $this->setUser($teacher); $event = \mod_forum\event\course_module_viewed::create(['context' => \context_module::instance($forum->cmid), 'objectid' => $forum->id]); $event->trigger(); $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($assign->cmid), 'objectid' => $assign->id]); $event->trigger(); // Ensure provider only fetches the user whose user context is checked. $userlist1 = new \core_privacy\local\request\userlist($studentcontext, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); $this->assertEquals($student, $userlist1->current()); $userlist2 = new \core_privacy\local\request\userlist($teachercontext, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); $this->assertEquals($teacher, $userlist2->current()); } /** * Test fetching information about user data stored. */ public function test_get_metadata(): void { $collection = new \core_privacy\local\metadata\collection('block_recentlyaccesseditems'); $newcollection = provider::get_metadata($collection); $itemcollection = $newcollection->get_collection(); $this->assertCount(1, $itemcollection); $table = reset($itemcollection); $this->assertEquals('block_recentlyaccesseditems', $table->get_name()); $privacyfields = $table->get_privacy_fields(); $this->assertCount(4, $privacyfields); $this->assertArrayHasKey('userid', $privacyfields); $this->assertArrayHasKey('courseid', $privacyfields); $this->assertArrayHasKey('cmid', $privacyfields); $this->assertArrayHasKey('timeaccess', $privacyfields); $this->assertEquals('privacy:metadata:block_recentlyaccesseditemstablesummary', $table->get_summary()); } /** * Test exporting data for an approved contextlist. */ public function test_export_user_data(): void { global $DB; $this->resetAfterTest(); $generator = $this->getDataGenerator(); $component = 'block_recentlyaccesseditems'; $student = $generator->create_user(); $studentcontext = \context_user::instance($student->id); // Enrol user in course and add course items. $course = $generator->create_course(); $generator->enrol_user($student->id, $course->id, 'student'); $forum = $generator->create_module('forum', ['course' => $course]); $assign = $generator->create_module('assign', ['course' => $course]); // Generate some recent activity. $this->setUser($student); $event = \mod_forum\event\course_module_viewed::create(['context' => \context_module::instance($forum->cmid), 'objectid' => $forum->id]); $event->trigger(); $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($assign->cmid), 'objectid' => $assign->id]); $event->trigger(); // Confirm data is present. $params = [ 'courseid' => $course->id, 'userid' => $student->id, ]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); // Export data for student. $approvedlist = new approved_contextlist($student, $component, [$studentcontext->id]); provider::export_user_data($approvedlist); // Confirm student's data is exported. $writer = \core_privacy\local\request\writer::with_context($studentcontext); $this->assertTrue($writer->has_any_data()); delete_course($course, false); $sc = \context_user::instance($student->id); $approvedlist = new approved_contextlist($student, $component, [$sc->id]); provider::export_user_data($approvedlist); $writer = \core_privacy\local\request\writer::with_context($sc); $this->assertTrue($writer->has_any_data()); } /** * Test exporting data for an approved contextlist with a deleted course */ public function test_export_user_data_with_deleted_course(): void { global $DB; $this->resetAfterTest(); $generator = $this->getDataGenerator(); $component = 'block_recentlyaccesseditems'; $student = $generator->create_user(); $studentcontext = \context_user::instance($student->id); // Enrol user in course and add course items. $course = $generator->create_course(); $generator->enrol_user($student->id, $course->id, 'student'); $forum = $generator->create_module('forum', ['course' => $course]); $assign = $generator->create_module('assign', ['course' => $course]); // Generate some recent activity. $this->setUser($student); $event = \mod_forum\event\course_module_viewed::create(['context' => \context_module::instance($forum->cmid), 'objectid' => $forum->id]); $event->trigger(); $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($assign->cmid), 'objectid' => $assign->id]); $event->trigger(); // Confirm data is present. $params = [ 'courseid' => $course->id, 'userid' => $student->id, ]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); delete_course($course, false); // Export data for student. $approvedlist = new approved_contextlist($student, $component, [$studentcontext->id]); provider::export_user_data($approvedlist); // Confirm student's data is exported. $writer = \core_privacy\local\request\writer::with_context($studentcontext); $this->assertFalse($writer->has_any_data()); } /** * Test deleting data for all users within an approved contextlist. */ public function test_delete_data_for_all_users_in_context(): void { global $DB; $this->resetAfterTest(); $generator = $this->getDataGenerator(); $student = $generator->create_user(); $studentcontext = \context_user::instance($student->id); $teacher = $generator->create_user(); // Enrol users in course and add course items. $course = $generator->create_course(); $generator->enrol_user($student->id, $course->id, 'student'); $generator->enrol_user($teacher->id, $course->id, 'teacher'); $forum = $generator->create_module('forum', ['course' => $course]); $assign = $generator->create_module('assign', ['course' => $course]); // Generate some recent activity for both users. $users = [$student, $teacher]; foreach ($users as $user) { $this->setUser($user); $event = \mod_forum\event\course_module_viewed::create(['context' => \context_module::instance($forum->cmid), 'objectid' => $forum->id]); $event->trigger(); $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($assign->cmid), 'objectid' => $assign->id]); $event->trigger(); } // Confirm data is present for both users. $params = [ 'courseid' => $course->id, 'userid' => $student->id, ]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); $params['userid'] = $teacher->id; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); // Attempt system context deletion (should have no effect). $systemcontext = \context_system::instance(); provider::delete_data_for_all_users_in_context($systemcontext); $params = ['courseid' => $course->id]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(4, $result); // Delete all data in student context. provider::delete_data_for_all_users_in_context($studentcontext); // Confirm only student data is deleted. $params = [ 'courseid' => $course->id, 'userid' => $student->id, ]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(0, $result); $params['userid'] = $teacher->id; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); } /** * Test deleting data within an approved contextlist for a user. */ public function test_delete_data_for_user(): void { global $DB; $this->resetAfterTest(); $generator = $this->getDataGenerator(); $component = 'block_recentlyaccesseditems'; $student = $generator->create_user(); $studentcontext = \context_user::instance($student->id); $teacher = $generator->create_user(); $teachercontext = \context_user::instance($teacher->id); // Enrol users in course and add course items. $course = $generator->create_course(); $generator->enrol_user($student->id, $course->id, 'student'); $generator->enrol_user($teacher->id, $course->id, 'teacher'); $forum = $generator->create_module('forum', ['course' => $course]); $assign = $generator->create_module('assign', ['course' => $course]); // Generate some recent activity for both users. $users = [$student, $teacher]; foreach ($users as $user) { $this->setUser($user); $event = \mod_forum\event\course_module_viewed::create(['context' => \context_module::instance($forum->cmid), 'objectid' => $forum->id]); $event->trigger(); $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($assign->cmid), 'objectid' => $assign->id]); $event->trigger(); } // Confirm data is present for both users. $params = [ 'courseid' => $course->id, 'userid' => $student->id, ]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); $params['userid'] = $teacher->id; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); // Attempt system context deletion (should have no effect). $systemcontext = \context_system::instance(); $approvedlist = new approved_contextlist($teacher, $component, [$systemcontext->id]); provider::delete_data_for_user($approvedlist); $params = ['courseid' => $course->id]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(4, $result); // Attempt to delete teacher data in student user context (should have no effect). $approvedlist = new approved_contextlist($teacher, $component, [$studentcontext->id]); provider::delete_data_for_user($approvedlist); $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(4, $result); // Delete teacher data in their own user context. $approvedlist = new approved_contextlist($teacher, $component, [$teachercontext->id]); provider::delete_data_for_user($approvedlist); // Confirm only teacher data is deleted. $params = [ 'courseid' => $course->id, 'userid' => $student->id, ]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); $params['userid'] = $teacher->id; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(0, $result); } /** * Test deleting data within a context for an approved userlist. */ public function test_delete_data_for_users(): void { global $DB; $this->resetAfterTest(); $generator = $this->getDataGenerator(); $component = 'block_recentlyaccesseditems'; $student = $generator->create_user(); $studentcontext = \context_user::instance($student->id); $teacher = $generator->create_user(); $teachercontext = \context_user::instance($teacher->id); // Enrol users in course and add course items. $course = $generator->create_course(); $generator->enrol_user($student->id, $course->id, 'student'); $generator->enrol_user($teacher->id, $course->id, 'teacher'); $forum = $generator->create_module('forum', ['course' => $course]); $assign = $generator->create_module('assign', ['course' => $course]); // Generate some recent activity for all users. $users = [$student, $teacher]; foreach ($users as $user) { $this->setUser($user); $event = \mod_forum\event\course_module_viewed::create(['context' => \context_module::instance($forum->cmid), 'objectid' => $forum->id]); $event->trigger(); $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($assign->cmid), 'objectid' => $assign->id]); $event->trigger(); } // Confirm data is present for all 3 users. $params = [ 'courseid' => $course->id, 'userid' => $student->id, ]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); $params['userid'] = $teacher->id; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); // Attempt system context deletion (should have no effect). $systemcontext = \context_system::instance(); $approvedlist = new approved_userlist($systemcontext, $component, [$student->id, $teacher->id]); provider::delete_data_for_users($approvedlist); $params = ['courseid' => $course->id]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(4, $result); // Attempt to delete data in another user's context (should have no effect). $approvedlist = new approved_userlist($studentcontext, $component, [$teacher->id]); provider::delete_data_for_users($approvedlist); $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(4, $result); // Delete users' data in teacher's context. $approvedlist = new approved_userlist($teachercontext, $component, [$student->id, $teacher->id]); provider::delete_data_for_users($approvedlist); // Confirm only teacher data is deleted. $params = [ 'courseid' => $course->id, 'userid' => $student->id, ]; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(2, $result); $params['userid'] = $teacher->id; $result = $DB->count_records('block_recentlyaccesseditems', $params); $this->assertEquals(0, $result); } } PK'9\v0recentlyaccesseditems/tests/externallib_test.phpnu[. namespace block_recentlyaccesseditems; use externallib_advanced_testcase; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); /** * Test Recently accessed items block external functions * * @package block_recentlyaccesseditems * @category external * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.6 */ final class externallib_test extends externallib_advanced_testcase { /** * Test the get_recent_items function. */ public function test_get_recent_items(): void { $this->resetAfterTest(); $generator = $this->getDataGenerator(); $this->setAdminUser(); // Add courses. $courses = array(); for ($i = 1; $i < 4; $i++) { $courses[] = $generator->create_course(); }; // Add users. $student = $generator->create_user(); $teacher = $generator->create_user(); // Enrol users and add items to courses. foreach ($courses as $course) { $generator->enrol_user($student->id, $course->id, 'student'); $forum[] = $this->getDataGenerator()->create_module('forum', array('course' => $course)); $glossary[] = $this->getDataGenerator()->create_module('glossary', array('course' => $course)); $assign[] = $this->getDataGenerator()->create_module('assign', ['course' => $course]); $h5pactivity[] = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]); } $generator->enrol_user($teacher->id, $courses[0]->id, 'teacher'); $this->setUser($student); // No recent items. $result = \block_recentlyaccesseditems\external::get_recent_items(); $this->assertCount(0, $result); // Student access all forums. foreach ($forum as $module) { $event = \mod_forum\event\course_module_viewed::create(array('context' => \context_module::instance($module->cmid), 'objectid' => $module->id)); $event->trigger(); $this->waitForSecond(); } // Test that only access to forums are returned. $result = \block_recentlyaccesseditems\external::get_recent_items(); $this->assertCount(count($forum), $result); // Student access all assignments. foreach ($assign as $module) { $event = \mod_assign\event\course_module_viewed::create(['context' => \context_module::instance($module->cmid), 'objectid' => $module->id]); $event->trigger(); $this->waitForSecond(); } // Student access all h5p. foreach ($h5pactivity as $module) { $event = \mod_h5pactivity\event\course_module_viewed::create( ['context' => \context_module::instance($module->cmid), 'objectid' => $module->id] ); $event->trigger(); $this->waitForSecond(); } // Test that results are sorted by timeaccess DESC (default). $result = \block_recentlyaccesseditems\external::get_recent_items(); $this->assertCount((count($forum) + count($assign) + count($h5pactivity)), $result); foreach ($result as $key => $record) { if ($key == 0) { continue; } $this->assertTrue($record->timeaccess < $result[$key - 1]->timeaccess); // Check that the branded property is set correctly. if ($record->modname == 'h5pactivity') { $this->assertTrue($record->branded); } else { $this->assertFalse($record->branded); } } // Delete a course and confirm it's activities don't get returned. delete_course($courses[0], false); $result = \block_recentlyaccesseditems\external::get_recent_items(); $this->assertCount((count($forum) + count($assign) + count($h5pactivity)) - 3, $result); // Delete a single course module should still return. course_delete_module($forum[1]->cmid); $result = \block_recentlyaccesseditems\external::get_recent_items(); $this->assertCount((count($forum) + count($assign) + count($h5pactivity)) - 4, $result); } } PK'9\N1g Orecentlyaccesseditems/tests/behat/block_recentlyaccesseditems_dashboard.featurenu[@block @block_recentlyaccesseditems @javascript Feature: The recently accessed items block allows users to easily access their most recently visited items In order to access the most recent items accessed As a user I can use the recently accessed items block in my dashboard Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | | Course 1 | C1 | | Course 2 | C2 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | And the following "activity" exists: | course | C1 | | activity | forum | | idnumber | Test forum name | | name | Test forum name | And I log in as "student1" @accessibility Scenario: User has not accessed any item Then I should see "No recent items" in the "Recently accessed items" "block" And the page should meet accessibility standards @accessibility Scenario: User has accessed some items Given I change window size to "large" When I am on the "Test forum name" "forum activity" page And I follow "Dashboard" Then I should see "Test forum name" in the "Recently accessed items" "block" And I should not see "Show more items" in the "Recently accessed items" "block" And the page should meet accessibility standards Scenario: User has accessed more than 3 items Given the following "activities" exist: | activity | name | intro | course | idnumber | | assign | Test assignment name | Test assignment description | C1 | assign1 | | book | Test book name | | C1 | book1 | | choice | Test choice name | Test choice description | C1 | choice1 | | data | Test database name | Test database description | C1 | data1 | And I change window size to "large" And I am on the "Test forum name" "forum activity" page And I am on the "Test database name" "data activity" page And I am on the "Test assignment name" "assign activity" page And I am on the "Test book name" "book activity" page And I am on the "Test choice name" "choice activity" page When I follow "Dashboard" Then I should see "Show more items" in the "Recently accessed items" "block" And I should not see "Test forum name" in the "Recently accessed items" "block" And I click on "Show more items" "button" in the "Recently accessed items" "block" And I should see "Test forum name" in the "Recently accessed items" "block" And I turn editing mode on And I am on homepage And I configure the "Recently accessed items" block And I set the following fields to these values: | Region | content | And I press "Save changes" And I turn editing mode off And I should not see "Show more items" in the "Recently accessed items" "block" PK'9\-(JJ5recentlyaccesseditems/block_recentlyaccesseditems.phpnu[. /** * Class definition for the Recently accessed items block. * * @package block_recentlyaccesseditems * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Recently accessed items block class. * * @package block_recentlyaccesseditems * @copyright Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_recentlyaccesseditems extends block_base { /** * Initialize class member variables */ public function init() { $this->title = get_string('pluginname', 'block_recentlyaccesseditems'); } /** * Returns the contents. * * @return stdClass contents of block */ public function get_content() { if (isset($this->content)) { return $this->content; } $renderable = new block_recentlyaccesseditems\output\main(); $renderer = $this->page->get_renderer('block_recentlyaccesseditems'); $this->content = new stdClass(); $this->content->text = $renderer->render($renderable); $this->content->footer = ''; return $this->content; } /** * Locations where block can be displayed. * * @return array */ public function applicable_formats() { return array('my' => true); } }PK'9\X>news_items/db/access.phpnu[. /** * News items block caps. * * @package block_news_items * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/news_items:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/news_items:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\'news_items/lang/en/block_news_items.phpnu[. /** * Strings for component 'block_news_items', language 'en', branch 'MOODLE_20_STABLE' * * @package block_news_items * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['news_items:addinstance'] = 'Add a new latest announcements block'; $string['news_items:myaddinstance'] = 'Add a new latest announcements block to Dashboard'; $string['pluginname'] = 'Latest announcements'; $string['privacy:metadata'] = 'The Latest announcements block only shows data stored in the forum and does not store data itself.'; PK'9\-news_items/version.phpnu[. /** * Version details * * @package block_news_items * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_news_items'; // Full name of the plugin (used for diagnostics) $plugin->dependencies = ['mod_forum' => 2025040800]; PK'9\lnews_items/block_news_items.phpnu[. /** * This file contains the news item block class, based upon block_base. * * @package block_news_items * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class block_news_items * * @package block_news_items * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_news_items extends block_base { function init() { $this->title = get_string('pluginname', 'block_news_items'); } function get_content() { global $CFG, $USER; if ($this->content !== NULL) { return $this->content; } $this->content = new stdClass; $this->content->text = ''; $this->content->footer = ''; if (empty($this->instance)) { return $this->content; } if ($this->page->course->newsitems) { // Create a nice listing of recent postings require_once($CFG->dirroot.'/mod/forum/lib.php'); // We'll need this $text = ''; if (!$forum = forum_get_course_forum($this->page->course->id, 'news')) { return ''; } $modinfo = get_fast_modinfo($this->page->course); if (empty($modinfo->instances['forum'][$forum->id])) { return ''; } $cm = $modinfo->instances['forum'][$forum->id]; if (!$cm->uservisible) { return ''; } $context = context_module::instance($cm->id); /// User must have perms to view discussions in that forum if (!has_capability('mod/forum:viewdiscussion', $context)) { return ''; } /// First work out whether we can post to this group and if so, include a link $groupmode = groups_get_activity_groupmode($cm); $currentgroup = groups_get_activity_group($cm, true); if (forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context)) { $text .= ''; } /// Get all the recent discussions we're allowed to see // This block displays the most recent posts in a forum in // descending order. The call to default sort order here will use // that unless the discussion that post is in has a timestart set // in the future. // This sort will ignore pinned posts as we want the most recent. $sort = forum_get_default_sort_order(true, 'p.modified', 'd', false); if (! $discussions = forum_get_discussions($cm, $sort, false, -1, $this->page->course->newsitems, false, -1, 0, FORUM_POSTS_ALL_USER_GROUPS) ) { $text .= '('.get_string('nonews', 'forum').')'; $this->content->text = $text; return $this->content; } /// Actually create the listing now $strposttimeformat = get_string('strftimedatetime', 'core_langconfig'); $strmore = get_string('more', 'forum'); /// Accessibility: markup as a list. $text .= "\n
    \n"; foreach ($discussions as $discussion) { $discussion->subject = $discussion->name; $discussion->subject = format_string($discussion->subject, true, $forum->course); $posttime = $discussion->modified; if (!empty($CFG->forum_enabletimedposts) && ($discussion->timestart > $posttime)) { $posttime = $discussion->timestart; } // If the user who created the discussion post has been deleted, indicate so. if ($discussion->userdeleted) { $userfullname = get_string('deleteduser', 'mod_forum'); } else { $userfullname = fullname($discussion, has_capability('moodle/site:viewfullnames', $context)); } $text .= '
  • '. '
    '. '
    '.userdate($posttime, $strposttimeformat).'
    '. '
    '.$userfullname.'
    '. '
    '. ''. "
  • \n"; } $text .= "
\n"; $this->content->text = $text; $this->content->footer = ''. get_string('oldertopics', 'forum').' ...'; /// If RSS is activated at site and forum level and this forum has rss defined, show link if (isset($CFG->enablerssfeeds) && isset($CFG->forum_enablerssfeeds) && $CFG->enablerssfeeds && $CFG->forum_enablerssfeeds && $forum->rsstype && $forum->rssarticles) { require_once($CFG->dirroot.'/lib/rsslib.php'); // We'll need this if ($forum->rsstype == 1) { $tooltiptext = get_string('rsssubscriberssdiscussions','forum'); } else { $tooltiptext = get_string('rsssubscriberssposts','forum'); } if (!isloggedin()) { $userid = $CFG->siteguest; } else { $userid = $USER->id; } $this->content->footer .= '
'.rss_get_link($context->id, $userid, 'mod_forum', $forum->id, $tooltiptext); } } return $this->content; } } PK'9\@P5>'news_items/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_news_items. * * @package block_news_items * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_news_items\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_news_items implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\b+news_items/tests/behat/display_news.featurenu[@block @block_news_items Feature: Latest announcements block displays the course latest news In order to be aware of the course announcements As a user I need to see the latest announcements block in the main course page @javascript Scenario: Latest course announcements are displayed and can be configured Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | category | newsitems | | Course 1 | C1 | 0 | 5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | news_items | Course | C1 | course-view-* | side-pre | And the following "mod_forum > discussions" exist: | user | forum | name | message | | teacher1 | Announcements | Discussion One | Not important | | teacher1 | Announcements | Discussion Two | Not important | | teacher1 | Announcements | Discussion Three | Not important | When I am on the "Course 1" Course page logged in as teacher1 Then I should see "Discussion One" in the "Latest announcements" "block" And I should see "Discussion Two" in the "Latest announcements" "block" And I should see "Discussion Three" in the "Latest announcements" "block" And I navigate to "Settings" in current page administration And I set the following fields to these values: | Number of announcements | 2 | And I press "Save and display" And I should not see "Discussion One" in the "Latest announcements" "block" And I should see "Discussion Two" in the "Latest announcements" "block" And I should see "Discussion Three" in the "Latest announcements" "block" And I navigate to "Settings" in current page administration And I set the following fields to these values: | Number of announcements | 0 | And I press "Save and display" And "Latest announcements" "block" should not exist PK'9\7`>  blog_menu/db/access.phpnu[. /** * Blog menu block caps. * * @package block_blog_menu * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/blog_menu:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\܉W%blog_menu/lang/en/block_blog_menu.phpnu[. /** * Strings for component 'block_blog_menu', language 'en', branch 'MOODLE_20_STABLE' * * @package block_blog_menu * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['blog_menu:addinstance'] = 'Add a new blog menu block'; $string['pluginname'] = 'Blog menu'; $string['privacy:metadata'] = 'The Blog menu block only shows data stored in other locations.'; PK'9\21blog_menu/version.phpnu[. /** * Version details * * @package block_blog_menu * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_blog_menu'; // Full name of the plugin (used for diagnostics) PK'9\ &&blog_menu/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_blog_menu. * * @package block_blog_menu * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_blog_menu\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_blog_menu implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\s'blog_menu/block_blog_menu.phpnu[. /** * Blog Menu Block page. * * @package block_blog_menu * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * The blog menu block class */ class block_blog_menu extends block_base { function init() { $this->title = get_string('pluginname', 'block_blog_menu'); } function instance_allow_multiple() { return true; } function has_config() { return false; } function applicable_formats() { return array('all' => true, 'my' => false, 'tag' => false); } function instance_allow_config() { return true; } function get_content() { global $CFG, $OUTPUT; // detect if blog enabled if ($this->content !== NULL) { return $this->content; } if (empty($CFG->enableblogs)) { $this->content = new stdClass(); $this->content->text = ''; if ($this->page->user_is_editing()) { $this->content->text = get_string('blogdisable', 'blog'); } return $this->content; } else if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL and (!isloggedin() or isguestuser())) { $this->content = new stdClass(); $this->content->text = ''; return $this->content; } // require necessary libs and get content require_once($CFG->dirroot .'/blog/lib.php'); // Prep the content $this->content = new stdClass(); $options = blog_get_all_options($this->page); if (count($options) == 0) { $this->content->text = ''; return $this->content; } // Iterate the option types $menulist = array(); foreach ($options as $types) { foreach ($types as $link) { $menulist[] = html_writer::link($link['link'], $link['string']); } $menulist[] = '
'; } // Remove the last element (will be an HR) array_pop($menulist); // Display the content as a list $this->content->text = html_writer::alist($menulist, array('class'=>'list')); // Prepare the footer for this block if (has_capability('moodle/blog:search', context_system::instance())) { $data = [ 'action' => new moodle_url('/blog/index.php'), 'inputname' => 'search', 'searchstring' => get_string('search', 'admin'), 'extraclasses' => 'mt-3' ]; $this->content->footer = $OUTPUT->render_from_template('core/search_input', $data); } else { // No footer to display $this->content->footer = ''; } // Return the content object return $this->content; } /** * Returns the role that best describes the blog menu block. * * @return string */ public function get_aria_role() { return 'navigation'; } /** * This block shouldn't be added to a page if the blogs advanced feature is disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { global $CFG; return $CFG->enableblogs; } } PK'9\m"blog_menu/tests/blog_menu_test.phpnu[. namespace block_blog_menu; use advanced_testcase; use block_blog_menu; use context_course; /** * PHPUnit block_blog_menu tests * * @package block_blog_menu * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_blog_menu */ final class blog_menu_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_blog_menu.php'); parent::setUpBeforeClass(); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_blog_menu(); // If blogs advanced feature is enabled, the method should return true. set_config('enableblogs', true); $this->assertTrue($block->can_block_be_added($page)); // However, if the blogs advanced feature is disabled, the method should return false. set_config('enableblogs', false); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\ȓ # #6blog_menu/tests/behat/block_blog_menu_activity.featurenu[@block @block_blog_menu Feature: Enable Block blog menu in an activity In order to enable the blog menu in an activity As a teacher I can add blog menu block to a course Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | Given the following "activity" exists: | activity | assign | | name | Test assignment 1 | | intro | Offline text | | course | C1 | | idnumber | 0001 | | section | 1 | | assignsubmission_file_enabled | 0 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | blog_menu | Activity module | 0001 | mod-assign-view | side-pre | Scenario: Students use the blog menu block to post blogs Given I am on the "Test assignment 1" "assign activity" page logged in as student1 And I follow "Add a new entry" When I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" Then I should see "S1 First Blog" And I should see "This is my awesome blog!" And I am on the "Test assignment 1" "assign activity" page And I follow "Blog entries" And I should see "S1 First Blog" And I should see "This is my awesome blog!" Scenario: Students use the blog menu block to view their blogs about the activity Given I am on the "Test assignment 1" "assign activity" page logged in as student1 And I follow "Add an entry about this Assignment" And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog about this Assignment! | And I press "Save changes" And I should see "S1 First Blog" And I should see "This is my awesome blog about this Assignment!" And I should see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page logged in as student2 And I follow "Add a new entry" And I set the following fields to these values: | Entry title | S2 Second Blog | | Blog entry body | My unrelated blog! | And I press "Save changes" And I should see "S2 Second Blog" And I should see "My unrelated blog!" And I should not see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page And I follow "Add an entry about this Assignment" And I set the following fields to these values: | Entry title | S2 First Blog | | Blog entry body | My course blog is better! | And I press "Save changes" And I should see "S2 First Blog" And I should see "My course blog is better!" And I should see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page When I follow "View my entries about this Assignment" Then I should see "S2 First Blog" And I should not see "S2 Second Blog" And I should not see "S1 First Blog" Scenario: Students use the blog menu block to view all blogs about the assignment Given I am on the "Test assignment 1" "assign activity" page logged in as student1 And I follow "Add an entry about this Assignment" And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog about this Assignment! | And I press "Save changes" And I should see "S1 First Blog" And I should see "This is my awesome blog about this Assignment!" And I should see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page logged in as student2 And I follow "Add a new entry" And I set the following fields to these values: | Entry title | S2 Second Blog | | Blog entry body | My unrelated blog! | And I press "Save changes" And I should see "S2 Second Blog" And I should see "My unrelated blog!" And I should not see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page And I follow "Add an entry about this Assignment" And I set the following fields to these values: | Entry title | S2 First Blog | | Blog entry body | My course blog is better! | And I press "Save changes" And I should see "S2 First Blog" And I should see "My course blog is better!" And I should see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page When I follow "View all entries about this Assignment" Then I should see "S1 First Blog" And I should see "S2 First Blog" And I should not see "S2 Second Blog" Scenario: Students use the blog menu block to view all their blog entries Given I am on the "Test assignment 1" "assign activity" page logged in as student1 And I follow "Add an entry about this Assignment" And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog about this Assignment! | And I press "Save changes" And I should see "S1 First Blog" And I should see "This is my awesome blog about this Assignment!" And I should see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page logged in as student2 And I follow "Add a new entry" And I set the following fields to these values: | Entry title | S2 Second Blog | | Blog entry body | My unrelated blog! | And I press "Save changes" And I should see "S2 Second Blog" And I should see "My unrelated blog!" And I should not see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page And I follow "Add an entry about this Assignment" And I set the following fields to these values: | Entry title | S2 First Blog | | Blog entry body | My course blog is better! | And I press "Save changes" And I should see "S2 First Blog" And I should see "My course blog is better!" And I should see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page When I follow "Blog entries" Then I should see "S2 First Blog" And I should see "S2 Second Blog" And I should not see "S1 First Blog" Scenario: Teacher searches for student blogs Given I am on the "Test assignment 1" "assign activity" page logged in as student1 And I follow "Add an entry about this Assignment" And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog about this Assignment! | And I press "Save changes" And I should see "S1 First Blog" And I should see "This is my awesome blog about this Assignment!" And I should see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page logged in as student2 And I follow "Add a new entry" And I set the following fields to these values: | Entry title | S2 Second Blog | | Blog entry body | My unrelated blog! | And I press "Save changes" And I should see "S2 Second Blog" And I should see "My unrelated blog!" And I should not see "Associated Assignment: Test assignment 1" And I am on the "Test assignment 1" "assign activity" page And I follow "Add an entry about this Assignment" And I set the following fields to these values: | Entry title | S2 First Blog | | Blog entry body | My course blog is better! | And I press "Save changes" And I should see "S2 First Blog" And I should see "My course blog is better!" And I should see "Associated Assignment: Test assignment 1" When I am on the "Test assignment 1" "assign activity" page logged in as teacher1 And I set the field "Search" to "First" And I press "Search" Then I should see "S1 First Blog" And I should see "S2 First Blog" And I should not see "S2 Second Blog" PK'9\ff7blog_menu/tests/behat/block_blog_menu_frontpage.featurenu[@block @block_blog_menu Feature: Enable Block blog menu on the frontpage In order to enable the blog menu on the frontpage As an admin I can add blog menu block to the frontpage Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | blog_menu | System | 1 | site-index | side-pre | Scenario: Students use the blog menu block to post blogs Given I log in as "student1" And I am on site homepage And I follow "Add a new entry" When I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" Then I should see "S1 First Blog" And I should see "This is my awesome blog!" And I am on site homepage And I follow "Blog entries" And I should see "S1 First Blog" And I should see "This is my awesome blog!" PK'9\(  -blog_menu/tests/behat/block_blog_menu.featurenu[@block @block_blog_menu Feature: Enable Block blog menu in a course In order to enable the blog menu in a course As a teacher I can add blog menu block to a course Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | Scenario: Add the block to a the course when blogs are disabled Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Blog menu" block And the following config values are set as admin: | enableblogs | 0 | And I reload the page Then I should see "Blogging is disabled!" in the "Blog menu" "block" Scenario: Add the block to a the course when blog associations are disabled Given I log in as "admin" And the following config values are set as admin: | useblogassociations | 0 | And I log out And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Blog menu" block Then I should see "Blog entries" in the "Blog menu" "block" And I should see "Add a new entry" in the "Blog menu" "block" And I should not see "View all entries for this course" in the "Blog menu" "block" And I should not see "View my entries about this course" in the "Blog menu" "block" And I should not see "Add an entry about this course" in the "Blog menu" "block" Scenario: Add the block to a the course when blog associations are enabled Given I log in as "admin" And the following config values are set as admin: | useblogassociations | 1 | And I log out And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Blog menu" block Then I should see "Blog entries" in the "Blog menu" "block" And I should see "Add a new entry" in the "Blog menu" "block" And I should see "View all entries for this course" in the "Blog menu" "block" And I should see "View my entries about this course" in the "Blog menu" "block" And I should see "Add an entry about this course" in the "Blog menu" "block" Scenario: Add the block to a the course when RSS is disabled Given I log in as "admin" And the following config values are set as admin: | enablerssfeeds | 0 | And I log out And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Blog menu" block Then I should not see "Blog RSS feed" in the "Blog menu" "block" And I should see "Add a new entry" in the "Blog menu" "block" Scenario: Add the block to a the course when RSS is enabled Given I log in as "admin" And the following config values are set as admin: | enablerssfeeds | 1 | And I log out And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Blog menu" block Then I should see "Blog RSS feed" in the "Blog menu" "block" And I should see "Add a new entry" in the "Blog menu" "block" PK'9\AM 3??4blog_menu/tests/behat/block_blog_menu_course.featurenu[@block @block_blog_menu Feature: Students can use block blog menu in a course In order students to use the blog menu in a course As a student I view blog menu block in a course Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | blog_menu | Course | C1 | course-view-* | side-pre | Scenario: Students use the blog menu block to post blogs Given I am on the "Course 1" course page logged in as student1 And I follow "Add a new entry" When I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" Then I should see "S1 First Blog" And I should see "This is my awesome blog!" And I am on "Course 1" course homepage And I follow "Blog entries" And I should see "S1 First Blog" And I should see "This is my awesome blog!" Scenario: Students use the blog menu block to view their blogs about the course Given I am on the "Course 1" course page logged in as student1 And I follow "Add an entry about this course" And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog about this course! | And I press "Save changes" And I should see "S1 First Blog" And I should see "This is my awesome blog about this course!" And I should see "Associated Course: C1" And I am on the "Course 1" course page logged in as student2 And I follow "Add a new entry" And I set the following fields to these values: | Entry title | S2 Second Blog | | Blog entry body | My unrelated blog! | And I press "Save changes" And I should see "S2 Second Blog" And I should see "My unrelated blog!" And I should not see "Associated Course: C1" And I am on "Course 1" course homepage And I follow "Add an entry about this course" And I set the following fields to these values: | Entry title | S2 First Blog | | Blog entry body | My course blog is better! | And I press "Save changes" And I should see "S2 First Blog" And I should see "My course blog is better!" And I should see "Associated Course: C1" And I am on "Course 1" course homepage When I follow "View my entries about this course" Then I should see "S2 First Blog" And I should not see "S2 Second Blog" And I should not see "S1 First Blog" Scenario: Students use the blog menu block to view all blogs about the course Given I am on the "Course 1" course page logged in as student1 And I follow "Add an entry about this course" And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog about this course! | And I press "Save changes" And I should see "S1 First Blog" And I should see "This is my awesome blog about this course!" And I should see "Associated Course: C1" And I am on the "Course 1" course page logged in as student2 And I follow "Add a new entry" And I set the following fields to these values: | Entry title | S2 Second Blog | | Blog entry body | My unrelated blog! | And I press "Save changes" And I should see "S2 Second Blog" And I should see "My unrelated blog!" And I should not see "Associated Course: C1" And I am on "Course 1" course homepage And I follow "Add an entry about this course" And I set the following fields to these values: | Entry title | S2 First Blog | | Blog entry body | My course blog is better! | And I press "Save changes" And I should see "S2 First Blog" And I should see "My course blog is better!" And I should see "Associated Course: C1" And I am on "Course 1" course homepage When I follow "View all entries for this course" Then I should see "S1 First Blog" And I should see "S2 First Blog" And I should not see "S2 Second Blog" Scenario: Students use the blog menu block to view all their blog entries Given I am on the "Course 1" course page logged in as student1 And I follow "Add an entry about this course" And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog about this course! | And I press "Save changes" And I should see "S1 First Blog" And I should see "This is my awesome blog about this course!" And I should see "Associated Course: C1" And I am on the "Course 1" course page logged in as student2 And I follow "Add a new entry" And I set the following fields to these values: | Entry title | S2 Second Blog | | Blog entry body | My unrelated blog! | And I press "Save changes" And I should see "S2 Second Blog" And I should see "My unrelated blog!" And I should not see "Associated Course: C1" And I am on "Course 1" course homepage And I follow "Add an entry about this course" And I set the following fields to these values: | Entry title | S2 First Blog | | Blog entry body | My course blog is better! | And I press "Save changes" And I should see "S2 First Blog" And I should see "My course blog is better!" And I should see "Associated Course: C1" And I am on "Course 1" course homepage When I follow "Blog entries" Then I should see "S2 First Blog" And I should see "S2 Second Blog" And I should not see "S1 First Blog" Scenario: Teacher searches for student blogs Given I am on the "Course 1" course page logged in as student1 And I follow "Add an entry about this course" And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog about this course! | And I press "Save changes" And I should see "S1 First Blog" And I should see "This is my awesome blog about this course!" And I should see "Associated Course: C1" And I am on the "Course 1" course page logged in as student2 And I follow "Add a new entry" And I set the following fields to these values: | Entry title | S2 Second Blog | | Blog entry body | My unrelated blog! | And I press "Save changes" And I should see "S2 Second Blog" And I should see "My unrelated blog!" And I should not see "Associated Course: C1" And I am on "Course 1" course homepage And I follow "Add an entry about this course" And I set the following fields to these values: | Entry title | S2 First Blog | | Blog entry body | My course blog is better! | And I press "Save changes" And I should see "S2 First Blog" And I should see "My course blog is better!" And I should see "Associated Course: C1" When I am on the "Course 1" course page logged in as teacher1 And I set the field "Search" to "First" And I press "Search" Then I should see "S1 First Blog" And I should see "S2 First Blog" And I should not see "S2 Second Blog" PK'9\2#? ? section_links/renderer.phpnu[. /** * Renderer for the section links block. * * @since Moodle 2.5 * @package block_section_links * @copyright 2013 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Renderer for the section links block. * * @package block_section_links * @copyright 2013 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_section_links_renderer extends plugin_renderer_base { /** * Render a series of section links. * * @param stdClass $course The course we are rendering for. * @param array $sections An array of section objects to render. * @param bool|int The section to provide a jump to link for. * @param bool $showsectionname Whether or not section name should be displayed. * @return string The HTML to display. */ public function render_section_links(stdClass $course, array $sections, $jumptosection = false, $showsectionname = false) { $olparams = $showsectionname ? ['class' => 'unlist'] : ['class' => 'inline-list']; $liparams = $showsectionname ? ['class' => 'mb-2'] : []; $html = html_writer::start_tag('ol', $olparams); foreach ($sections as $section) { $attributes = array(); if (!$section->visible) { $attributes['class'] = 'dimmed'; } $html .= html_writer::start_tag('li', $liparams); $sectiontext = $section->section; if ($showsectionname) { $sectiontext = $section->name; } if ($section->highlight) { $sectiontext = html_writer::tag('strong', $sectiontext); } $html .= html_writer::link( course_get_url($course, $section->section, ['navigation' => true]), $sectiontext, $attributes ); $html .= html_writer::end_tag('li').' '; } $html .= html_writer::end_tag('ol'); if ($jumptosection && isset($sections[$jumptosection])) { if ($course->format == 'weeks') { $linktext = new lang_string('jumptocurrentweek', 'block_section_links'); } else if ($course->format == 'topics') { $linktext = new lang_string('jumptocurrenttopic', 'block_section_links'); } $attributes = array(); if (!$sections[$jumptosection]->visible) { $attributes['class'] = 'dimmed'; } $html .= html_writer::link(course_get_url($course, $jumptosection, ['navigation' => true]), $linktext, $attributes); } return $html; } } PK'9\qsection_links/db/access.phpnu[. /** * Section links block caps. * * @package block_section_links * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/section_links:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\_isection_links/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the section links block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @since Moodle 2.5 * @package block_section_links * @copyright 2013 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade code for the section links block. * * @global moodle_database $DB * @param int $oldversion * @param object $block */ function xmldb_block_section_links_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\֏``-section_links/lang/en/block_section_links.phpnu[. /** * Strings for component 'block_section_links', language 'en', branch 'MOODLE_20_STABLE' * * @package block_section_links * @copyright Jason Hardin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['incby1'] = 'Increase by'; $string['incby1_help'] = 'This is the value the section is incremented each time a section link is displayed starting at 1.'; $string['incby2'] = 'Alternative increase by'; $string['incby2_help'] = 'This is the value the section is incremented each time a section link is displayed starting at 1.'; $string['jumptocurrenttopic'] = 'Jump to highlighted section'; $string['jumptocurrentweek'] = 'Jump to current week'; $string['numsections1'] = 'Number of sections'; $string['numsections1_help'] = 'Once the number of sections in the course reaches this number then the increment by value is used.'; $string['numsections2'] = 'Alternative number of sections'; $string['numsections2_help'] = 'Once the number of sections in the course reaches this number then the Alternative increment by value is used.'; $string['pluginname'] = 'Section links'; $string['section_links:addinstance'] = 'Add a new section links block'; $string['showsectionname'] = 'Display section name'; $string['showsectionname_help'] = 'If set to Yes, section names are used as links. Otherwise, numbers are used.'; $string['privacy:metadata'] = 'The Section links block only shows data stored in other locations.'; PK'9\  section_links/edit_form.phpnu[. /** * Instance configuration for the section links block. * * @package block_section_links * @copyright 2013 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Instance configuration form. * * @package block_section_links * @copyright 2013 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_section_links_edit_form extends block_edit_form { /** * The definition of the fields to use. * * @param MoodleQuickForm $mform */ protected function specific_definition($mform) { $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $numberofsections = array(); for ($i = 1; $i < 53; $i++){ $numberofsections[$i] = $i; } $increments = array(); for ($i = 1; $i < 11; $i++){ $increments[$i] = $i; } $config = get_config('block_section_links'); $selected = array( 1 => array(22, 2), 2 => array(40, 5), ); if (!empty($config->numsections1)) { if (empty($config->incby1)) { $config->incby1 = $selected[1][1]; } $selected[1] = array($config->numsections1, $config->incby1); } if (!empty($config->numsections2)) { if (empty($config->incby1)) { $config->incby1 = $selected[2][1]; } $selected[2] = array($config->numsections2, $config->incby2); } for ($i = 1; $i < 3; $i++) { $mform->addElement('select', 'config_numsections'.$i, get_string('numsections'.$i, 'block_section_links'), $numberofsections); $mform->setDefault('config_numsections'.$i, $selected[$i][0]); $mform->setType('config_numsections'.$i, PARAM_INT); $mform->addHelpButton('config_numsections'.$i, 'numsections'.$i, 'block_section_links'); $mform->addElement('select', 'config_incby'.$i, get_string('incby'.$i, 'block_section_links'), $increments); $mform->setDefault('config_incby'.$i, $selected[$i][1]); $mform->setType('config_incby'.$i, PARAM_INT); $mform->addHelpButton('config_incby'.$i, 'incby'.$i, 'block_section_links'); } $mform->addElement('selectyesno', 'config_showsectionname', get_string('showsectionname', 'block_section_links')); $mform->setDefault('config_showsectionname', !empty($config->showsectionname) ? 1 : 0); $mform->addHelpButton('config_showsectionname', 'showsectionname', 'block_section_links'); } }PK'9\3'z%section_links/block_section_links.phpnu[. /** * This file contains the main class for the section links block. * * @package block_section_links * @copyright Jason Hardin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Section links block class. * * @package block_section_links * @copyright Jason Hardin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_section_links extends block_base { /** * Initialises the block instance. */ public function init() { $this->title = get_string('pluginname', 'block_section_links'); } /** * Returns an array of formats for which this block can be used. * * @return array */ public function applicable_formats() { return [ 'course-view-weeks' => true, 'course-view-topics' => true, 'course-view-section-weeks' => true, 'course-view-section-topics' => true, ]; } /** * Generates the content of the block and returns it. * * If the content has already been generated then the previously generated content is returned. * * @return stdClass */ public function get_content() { // The config should be loaded by now. // If its empty then we will use the global config for the section links block. if (isset($this->config)){ $config = $this->config; } else{ $config = get_config('block_section_links'); } if ($this->content !== null) { return $this->content; } $this->content = new stdClass; $this->content->footer = ''; $this->content->text = ''; if (empty($this->instance)) { return $this->content; } $course = $this->page->course; $courseformat = course_get_format($course); $numsections = $courseformat->get_last_section_number(); $context = context_course::instance($course->id); // Course format options 'numsections' is required to display the block. if (empty($numsections)) { return $this->content; } // Prepare the increment value. if (!empty($config->numsections1) and ($numsections > $config->numsections1)) { $inc = $config->incby1; } else if ($numsections > 22) { $inc = 2; } else { $inc = 1; } if (!empty($config->numsections2) and ($numsections > $config->numsections2)) { $inc = $config->incby2; } else { if ($numsections > 40) { $inc = 5; } } // Whether or not section name should be displayed. $showsectionname = !empty($config->showsectionname) ? true : false; // Prepare an array of sections to create links for. $sections = []; $canviewhidden = has_capability('moodle/course:update', $context); $coursesections = $courseformat->get_sections(); $coursesectionscount = count($coursesections); $sectiontojumpto = false; for ($i = $inc; $i <= $coursesectionscount; $i += $inc) { if ($i > $numsections || !isset($coursesections[$i])) { continue; } $section = $coursesections[$i]; // Delegated sections (like subsections) are not listed in the block. if ($section->get_component_instance() !== null) { continue; } if ($section->section && ($section->visible || $canviewhidden)) { $sections[$i] = (object) [ 'section' => $section->section, 'visible' => $section->visible, 'highlight' => false ]; if ($courseformat->is_section_current($section)) { $sections[$i]->highlight = true; $sectiontojumpto = $section->section; } if ($showsectionname) { $sections[$i]->name = $courseformat->get_section_name($i); } } } if (!empty($sections)) { // Render the sections. $renderer = $this->page->get_renderer('block_section_links'); $this->content->text = $renderer->render_section_links($this->page->course, $sections, $sectiontojumpto, $showsectionname); } return $this->content; } /** * Returns true if this block has instance config. * * @return bool **/ public function instance_allow_config() { return true; } /** * Returns true if this block has global config. * * @return bool */ public function has_config() { return true; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $instanceconfigs = !empty($this->config) ? $this->config : new stdClass(); $pluginconfigs = get_config('block_section_links'); return (object) [ 'instance' => $instanceconfigs, 'plugin' => $pluginconfigs, ]; } } PK'9\ {Z)section_links/version.phpnu[. /** * Version details * * @package block_section_links * @copyright Jason Hardin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_section_links'; // Full name of the plugin (used for diagnostics) PK'9\*section_links/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_section_links. * * @package block_section_links * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_section_links\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_section_links implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\$=\bbsection_links/upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This file describes API changes in the section_links block code. === 3.11 === * New optional parameter $showsectionname has been added to render_section_links(). Setting this to true will display section name in addition to section number. PK'9\M 3section_links/tests/behat/show_section_name.featurenu[@block @block_section_links Feature: The Section links block can be configured to display section name in addition to section number Background: Given the following "course" exists: | fullname | Course 1 | | shortname | C1 | | category | 0 | | numsections | 10 | | coursedisplay | 1 | | initsections | 1 | And the following "activities" exist: | activity | name | course | idnumber | section | | assign | First assignment | C1 | assign1 | 7 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following config values are set as admin: | showsectionname | 1 | block_section_links | | unaddableblocks | | theme_boost| And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | section_links | Course | C1 | course-view-* | side-pre | Scenario: Student can see section name under the Section links block When I am on the "Course 1" course page logged in as student1 Then I should see "Section 7" in the "Section links" "block" And I should not see "7: Section 7" in the "Section links" "block" And I follow "Section 7" And I should see "First assignment" Scenario: Teacher can configure existing Section links block to display section number or section name Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I configure the "Section links" block And I set the following fields to these values: | Display section name | No | And I click on "Save changes" "button" Then I should not see "7: Section 7" in the "Section links" "block" And I should not see "Section 7" in the "Section links" "block" And I should see "7" in the "Section links" "block" And I follow "7" And I should see "First assignment" Scenario: Subsections names are not displayed in the Section links block Given the following "activity" exists: | activity | subsection | | name | Subsection1 | | course | C1 | | idnumber | subsection1 | | section | 1 | When I am on the "Course 1" course page logged in as student1 Then I should not see "Subsection1" in the "Section links" "block" PK'9\-+v v <section_links/tests/behat/block_section_links_course.featurenu[@block @block_section_links Feature: The section links block allows users to quickly navigate around a moodle course In order to navigate a moodle course As a teacher I can use the section links block Background: Given the following "courses" exist: | fullname | shortname | category | numsections | coursedisplay | | Course 1 | C1 | 0 | 20 | 1 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | name | intro | course | section | idnumber | assignsubmission_file_enabled | | assign | Test assignment 1 | Offline text | C1 | 5 | assign1 | 0 | And the following config values are set as admin: | showsectionname | 0 | block_section_links | | unaddableblocks | | theme_boost | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Add the section links block to a course. Given I add the "Section links" block And I turn editing mode off And I should see "5" in the "Section links" "block" When I follow "5" Then I should see "Test assignment 1" Scenario: Add the section links block to a course and limit the sections displayed. Given I add the "Section links" block And I configure the "Section links" block And I set the following fields to these values: | config_numsections1 | 5 | | config_incby1 | 5 | | config_numsections2 | 40 | | config_incby2 | 10 | And I press "Save changes" And I turn editing mode off And I should see "5" in the "Section links" "block" When I follow "5" Then I should see "Test assignment 1" Scenario: Add the section links block to a course and limit the sections displayed using the alternative number of sections. Given I add the "Section links" block And I configure the "Section links" block And I set the following fields to these values: | config_numsections1 | 5 | | config_incby1 | 1 | | config_numsections2 | 10 | | config_incby2 | 5 | And I press "Save changes" And I turn editing mode off And I should see "5" in the "Section links" "block" When I follow "5" Then I should see "Test assignment 1" Scenario: Subsections numbers are not displayed in the Section links block Given the following "activity" exists: | activity | subsection | | name | Subsection1 | | course | C1 | | idnumber | subsection1 | | section | 1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | section_links | Course | C1 | course-view-* | side-pre | When I am on "Course 1" course homepage Then "21" "link" should not exist in the "Section links" "block" PK'9\ G+section_links/settings.phpnu[. /** * Section links block * * @package block_section_links * @copyright Jason Hardin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { $numberofsections = array(); for ($i = 1; $i < 53; $i++){ $numberofsections[$i] = $i; } $increments = array(); for ($i = 1; $i < 11; $i++){ $increments[$i] = $i; } $selected = array(1 => array(22,2), 2 => array(40,5)); for($i = 1; $i < 3; $i++){ $settings->add(new admin_setting_configselect('block_section_links/numsections'.$i, get_string('numsections'.$i, 'block_section_links'), get_string('numsections'.$i.'_help', 'block_section_links'), $selected[$i][0], $numberofsections)); $settings->add(new admin_setting_configselect('block_section_links/incby'.$i, get_string('incby'.$i, 'block_section_links'), get_string('incby'.$i.'_help', 'block_section_links'), $selected[$i][1], $increments)); } $settings->add( new admin_setting_configcheckbox('block_section_links/showsectionname', get_string('showsectionname', 'block_section_links'), get_string('showsectionname_help', 'block_section_links'), 1), ); } PK'9\T1 1 private_files/styles.cssnu[/* Rule so that the table tree view works with word-wrap: break-word. */ .block_private_files .content table { table-layout: fixed; width: 100%; } .block_private_files .content .footer { padding: 10px 0 0; margin-top: .5em; } .block_private_files ul[role="tree"] { margin: 0; padding: 0; } .block_private_files ul, .block_private_files li { list-style: none; } .block_private_files [role="treeitem"] { padding-left: 22px; cursor: pointer; } .block_private_files [role="treeitem"] p { margin-bottom: 0; } .block_private_files [role="treeitem"][aria-expanded] { padding-left: 0; } .block_private_files [role="treeitem"][aria-expanded="false"] > p { /*rtl:remove*/ background-image: url('[[pix:t/collapsed]]'); /*rtl:raw: background-image: url('[[pix:t/collapsed_rtl]]'); */ background-size: 16px 16px; background-position: 1px 1px; background-repeat: no-repeat; padding-left: 22px; } .block_private_files [role="treeitem"][aria-expanded="true"] > p { background-image: url('[[pix:t/expanded]]'); background-size: 16px 16px; background-position: 1px 1px; background-repeat: no-repeat; padding-left: 22px; } .block_private_files [role="treeitem"]:not([aria-expanded]) { background-image: repeating-linear-gradient( to right, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .5) 1px, rgba(255, 255, 255, 0) 1px, rgba(255, 255, 255, 0) 2px ), repeating-linear-gradient( to top, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.5) 1px, rgba(255, 255, 255, 0) 1px, rgba(255, 255, 255, 0) 2px ); background-repeat: no-repeat, no-repeat; /*rtl:remove*/ background-position: left 10px top 50%, left 8px top 0; /*rtl:raw: background-position: right 10px top 50%, right 8px top 0; */ background-size: 11px 1px, 1px 100%; } .block_private_files [role="treeitem"]:not([aria-expanded]):last-child { background-size: 11px 1px, 1px 50%; } .block_private_files [role="group"] { background-image: repeating-linear-gradient( to top, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.5) 1px, rgba(255, 255, 255, 0) 1px, rgba(255, 255, 255, 0) 2px ); background-repeat: no-repeat; background-position: left 8px top 100%; background-size: 1px 100%; padding-left: 22px; margin-left: 0; } .block_private_files [aria-hidden="true"]:not(.icon) { display: none; } PK'9\|private_files/renderer.phpnu[. /** * Print private files tree * * @package block_private_files * @copyright 2010 Dongsheng Cai * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); class block_private_files_renderer extends plugin_renderer_base { /** * Prints private files tree view * @return string */ public function private_files_tree() { return $this->render(new private_files_tree); } public function render_private_files_tree(private_files_tree $tree) { if (empty($tree->dir['subdirs']) && empty($tree->dir['files'])) { $html = $this->output->box(get_string('nofilesavailable', 'repository')); } else { $htmlid = 'private_files_tree_'.uniqid(); $this->page->requires->js_call_amd('block_private_files/files_tree', 'init', [$htmlid]); $html = '
'; $html .= $this->htmllize_tree($tree, $tree->dir, true); $html .= '
'; } return $html; } /** * Internal function - creates htmls structure suitable for core/tree AMD. * * @param private_files_tree $tree The renderable tree. * @param array $dir The directory in the tree * @param bool $isroot If it is the root directory in the tree. * @return string */ protected function htmllize_tree($tree, $dir, $isroot) { global $CFG; if (empty($dir['subdirs']) and empty($dir['files'])) { return ''; } if ($isroot) { $result = '
    '; } else { $result = ''; return $result; } } class private_files_tree implements renderable { public $context; public $dir; public function __construct() { global $USER; $this->context = context_user::instance($USER->id); $fs = get_file_storage(); $this->dir = $fs->get_area_tree($this->context->id, 'user', 'private', 0); } } PK'9\@Z!!private_files/db/access.phpnu[. /** * Private files block caps. * * @package block_private_files * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/private_files:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/private_files:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\#"66-private_files/lang/en/block_private_files.phpnu[. /** * Strings for component 'block_private_files', language 'en', branch 'MOODLE_20_STABLE' * * @package block_private_files * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['pluginname'] = 'Private files'; $string['privatefiles'] = 'Private files'; $string['private_files:addinstance'] = 'Add a new private files block'; $string['private_files:myaddinstance'] = 'Add a new private files block to Dashboard'; $string['privacy:metadata'] = 'The Private files block only provides a view of, and a link to, the user\'s private files.'; PK'9\غCprivate_files/version.phpnu[. /** * Version details * * @package block_private_files * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_private_files'; // Full name of the plugin (used for diagnostics) PK'9\ *private_files/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_private_files. * * @package block_private_files * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_private_files\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_private_files implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\o0&&private_files/edit.phpnu[. /** * Manage files in folder in private area. * * This page is not used and now redirects to the page to manage the private files. * * @package block_private_files * @copyright 2010 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require('../../config.php'); redirect(new moodle_url('/user/files.php')); PK'9\#private_files/amd/src/files_tree.jsnu[// 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 . /** * Changes the display of directories and files into a tree. * * @module block_private_files/files_tree * @copyright 2021 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import Tree from 'core/tree'; /** * The init function that does the job. * It changes the display of directories and files into a tree. * * @param {string} blockId */ export const init = (blockId) => { new Tree(`#${blockId} [role="tree"]`); }; PK'9\kff)private_files/amd/build/files_tree.min.jsnu[define("block_private_files/files_tree",["exports","core/tree"],(function(_exports,_tree){var obj; /** * Changes the display of directories and files into a tree. * * @module block_private_files/files_tree * @copyright 2021 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_tree=(obj=_tree)&&obj.__esModule?obj:{default:obj};_exports.init=blockId=>{new _tree.default("#".concat(blockId,' [role="tree"]'))}})); //# sourceMappingURL=files_tree.min.js.mapPK'9\{]jj-private_files/amd/build/files_tree.min.js.mapnu[{"version":3,"file":"files_tree.min.js","sources":["../src/files_tree.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Changes the display of directories and files into a tree.\n *\n * @module block_private_files/files_tree\n * @copyright 2021 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Tree from 'core/tree';\n\n/**\n * The init function that does the job.\n * It changes the display of directories and files into a tree.\n *\n * @param {string} blockId\n */\nexport const init = (blockId) => {\n new Tree(`#${blockId} [role=\"tree\"]`);\n};\n"],"names":["blockId","Tree"],"mappings":";;;;;;;oJA8BqBA,cACbC,yBAASD"}PK'9\i)private_files/tests/fixtures/testfile.txtnu[This is a test file PK'9\ퟠOO<private_files/tests/behat/block_private_files_course.featurenu[@block @block_private_files @_file_upload @javascript Feature: The private files block allows users to store files privately in moodle on course page In order to store a private file in moodle As a teacher I can upload the file to my private files area using the private files block in a course Scenario: Upload a file to the private files block from a course Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | private_files | Course | C1 | course-view-* | side-pre | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I should see "No files available" in the "Private files" "block" When I follow "Manage private files..." And I upload "blocks/private_files/tests/fixtures/testfile.txt" file to "Files" filemanager And I press "Save changes" Then I should see "testfile.txt" in the "Private files" "block" PK'9\JH?private_files/tests/behat/block_private_files_frontpage.featurenu[@block @block_private_files @_file_upload Feature: The private files block allows users to store files privately in moodle on front page. In order to store a private file in moodle As a teacher I can upload the file to my private files area using the private files block from the front page Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | private_files | System | 1 | site-index | side-pre | Scenario: Try to view the private files block as a guest Given I log in as "guest" When I am on site homepage Then "Private files" "block" should not exist @javascript Scenario: Upload a file to the private files block from the frontpage Given I log in as "teacher1" And I am on site homepage And "Private files" "block" should exist And I should see "No files available" in the "Private files" "block" When I follow "Manage private files..." And I upload "blocks/private_files/tests/fixtures/testfile.txt" file to "Files" filemanager And I press "Save changes" Then I should see "testfile.txt" in the "Private files" "block" PK'9\-ϝ>private_files/tests/behat/block_private_files_activity.featurenu[@block @block_private_files @_file_upload @javascript Feature: The private files block allows users to store files privately in moodle on activity page In order to store a private file in moodle As a teacher I can upload the file to my private files area using the private files block in an activity Scenario: Upload a file to the private files block in an activity Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | course | idnumber | name | intro | | page | C1 | page1 | Test page name | Test page description | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | private_files | Activity module | page1 | mod-page-* | side-pre | And I am on the "Test page name" "page activity" page logged in as teacher1 And I should see "No files available" in the "Private files" "block" When I follow "Manage private files..." And I upload "blocks/private_files/tests/fixtures/testfile.txt" file to "Files" filemanager And I press "Save changes" Then I should see "testfile.txt" in the "Private files" "block" PK'9\Z.[[?private_files/tests/behat/block_private_files_dashboard.featurenu[@block @block_private_files @_file_upload @javascript Feature: The private files block allows users to store files privately in moodle on dashboard In order to store a private file in moodle As a user I can upload the file to my private files area using the private files block on the dashboard Scenario: Upload a file to the private files block from the dashboard Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | private_files | System | 1 | my-index | side-post | And I log in as "teacher1" And "Private files" "block" should exist And I should see "No files available" in the "Private files" "block" When I follow "Manage private files..." And I upload "blocks/private_files/tests/fixtures/testfile.txt" file to "Files" filemanager And I press "Save changes" Then I should see "testfile.txt" in the "Private files" "block" PK'9\H %private_files/block_private_files.phpnu[. /** * Manage user private area files * * @package block_private_files * @copyright 2010 Dongsheng Cai * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_private_files extends block_base { function init() { $this->title = get_string('pluginname', 'block_private_files'); } function specialization() { } function applicable_formats() { return array('all' => true); } function instance_allow_multiple() { return false; } function get_content() { if ($this->content !== NULL) { return $this->content; } if (empty($this->instance)) { return null; } $this->content = new stdClass(); $this->content->text = ''; $this->content->footer = ''; if (isloggedin() && !isguestuser()) { // Show the block $this->content = new stdClass(); //TODO: add capability check here! $renderer = $this->page->get_renderer('block_private_files'); $this->content->text = $renderer->private_files_tree(); if (has_capability('moodle/user:manageownfiles', $this->context)) { $this->content->footer = html_writer::link( new moodle_url('/user/files.php'), get_string('privatefilesmanage') . '...', ['data-action' => 'manageprivatefiles']); $this->page->requires->js_call_amd( 'core_user/private_files', 'initModal', ['[data-action=manageprivatefiles]', \core_user\form\private_files::class] ); } } return $this->content; } } PK'9\66tag_flickr/styles.cssnu[.block_tag_flickr .flickr-photos { padding: 3px; }PK'9\F tag_flickr/db/access.phpnu[. /** * Tag flickr block caps. * * @package block_tag_flickr * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/tag_flickr:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\!K'tag_flickr/lang/en/block_tag_flickr.phpnu[. /** * Strings for component 'block_tag_flickr', language 'en', branch 'MOODLE_20_STABLE' * * @package block_tag_flickr * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['configtitle'] = 'Flickr block title'; $string['date-posted-asc'] = 'Date posted ASC'; $string['date-posted-desc'] = 'Date posted DESC'; $string['date-taken-asc'] = 'Date taken ASC'; $string['date-taken-desc'] = 'Date taken DESC'; $string['defaulttile'] = 'Flickr'; $string['getfromphotoset'] = 'Get photos from photoset with id'; $string['includerelatedtags'] = 'Include related tags in query'; $string['interestingness-asc'] = 'Interestingness ASC'; $string['interestingness-desc'] = 'Interestingness DESC'; $string['numberofphotos'] = 'Number of photos'; $string['pluginname'] = 'Flickr'; $string['relevance'] = 'Relevance'; $string['sortby'] = 'Sort by'; $string['tag_flickr:addinstance'] = 'Add a new flickr block'; $string['privacy:metadata:block_tag_flickr'] = 'The Flickr block plugin does not store any personal data, but does transmit user data from Moodle to the remote system.'; $string['privacy:metadata:block_tag_flickr:tags'] = 'The tag values sent as CSV format to search for Flickr images.'; PK'9\Z tag_flickr/edit_form.phpnu[. /** * Form for editing tag_flickr block instances. * * @package block_tag_flickr * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Form for editing tag_flickr block instances. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_tag_flickr_edit_form extends block_edit_form { protected function specific_definition($mform) { $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $mform->addElement('text', 'config_title', get_string('configtitle', 'block_tag_flickr')); $mform->setType('config_title', PARAM_TEXT); $mform->addElement('text', 'config_numberofphotos', get_string('numberofphotos', 'block_tag_flickr'), array('size' => 5)); $mform->setType('config_numberofphotos', PARAM_INT); $mform->addElement('selectyesno', 'config_includerelatedtags', get_string('includerelatedtags', 'block_tag_flickr')); $mform->setDefault('config_includerelatedtags', 0); $sortoptions = array( 'date-posted-asc' => get_string('date-posted-asc', 'block_tag_flickr'), 'date-posted-desc' => get_string('date-posted-desc', 'block_tag_flickr'), 'date-taken-asc' => get_string('date-taken-asc', 'block_tag_flickr'), 'date-taken-desc' => get_string('date-taken-desc', 'block_tag_flickr'), 'interestingness-asc' => get_string('interestingness-asc', 'block_tag_flickr'), 'interestingness-desc' => get_string('interestingness-desc', 'block_tag_flickr'), 'relevance' => get_string('relevance', 'block_tag_flickr'), ); $mform->addElement('select', 'config_sortby', get_string('sortby', 'block_tag_flickr'), $sortoptions); $mform->setDefault('config_sortby', 'relevance'); $mform->addElement('text', 'config_photoset', get_string('getfromphotoset', 'block_tag_flickr')); $mform->setType('config_photoset', PARAM_ALPHANUM); } } PK'9\Umtag_flickr/version.phpnu[. /** * Version details * * @package block_tag_flickr * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_tag_flickr'; // Full name of the plugin (used for diagnostics) PK'9\[tag_flickr/block_tag_flickr.phpnu[. /** * Flickr tag block. * * @package block_tag_flickr * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); define('FLICKR_DEV_KEY', '4fddbdd7ff2376beec54d7f6afad425e'); define('DEFAULT_NUMBER_OF_PHOTOS', 6); require_once("{$CFG->libdir}/flickrclient.php"); class block_tag_flickr extends block_base { function init() { $this->title = get_string('pluginname','block_tag_flickr'); } function applicable_formats() { return array('tag' => true); } function specialization() { $this->title = !empty($this->config->title) ? $this->config->title : get_string('pluginname', 'block_tag_flickr'); } function instance_allow_multiple() { return true; } function get_content() { global $CFG, $USER; //note: do NOT include files at the top of this file require_once($CFG->libdir . '/filelib.php'); if ($this->content !== NULL) { return $this->content; } $tagid = optional_param('id', 0, PARAM_INT); // tag id - for backware compatibility $tag = optional_param('tag', '', PARAM_TAG); // tag $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id. if ($tagid) { $tagobject = core_tag_tag::get($tagid); } else if ($tag) { $tagobject = core_tag_tag::get_by_name($tc, $tag); } if (empty($tagobject)) { $this->content = new stdClass; $this->content->text = ''; $this->content->footer = ''; return $this->content; } //include related tags in the photo query ? $tagscsv = $tagobject->name; if (!empty($this->config->includerelatedtags)) { foreach ($tagobject->get_related_tags() as $t) { $tagscsv .= ',' . $t->get_display_name(false); } } $tagscsv = urlencode($tagscsv); //number of photos to display $numberofphotos = DEFAULT_NUMBER_OF_PHOTOS; if( !empty($this->config->numberofphotos)) { $numberofphotos = $this->config->numberofphotos; } //sort search results by $sortby = 'relevance'; if( !empty($this->config->sortby)) { $sortby = $this->config->sortby; } //pull photos from a specific photoset if(!empty($this->config->photoset)){ $request = 'https://api.flickr.com/services/rest/?method=flickr.photosets.getPhotos'; $request .= '&api_key='.FLICKR_DEV_KEY; $request .= '&photoset_id='.$this->config->photoset; $request .= '&per_page='.$numberofphotos; $request .= '&format=json'; // We need to add nojsoncallback=? here, otherwise, Flickr will return the jsonFlickrApi object. $request .= '&nojsoncallback=?'; $response = $this->fetch_request($request); $search = @json_decode($response, true); if (!is_array($search) || json_last_error() !== JSON_ERROR_NONE) { // The response didn't appear to be in correct format. return; } foreach ($search['photoset']['photo'] as $p){ $p['owner'] = $search['photoset']['owner']; } $photos = array_values($search['photoset']['photo']); } //search for photos tagged with $tagscsv else{ $request = 'https://api.flickr.com/services/rest/?method=flickr.photos.search'; $request .= '&api_key='.FLICKR_DEV_KEY; $request .= '&tags='.$tagscsv; $request .= '&per_page='.$numberofphotos; $request .= '&sort='.$sortby; $request .= '&format=json'; // We need to add nojsoncallback=? here, otherwise, Flickr will return the jsonFlickrApi object. $request .= '&nojsoncallback=?'; $response = $this->fetch_request($request); $search = @json_decode($response, true); if (!is_array($search) || json_last_error() !== JSON_ERROR_NONE) { // The response didn't appear to be in correct format. return; } $photos = array_values($search['photos']['photo']); } if(strcmp($search['stat'], 'ok') != 0) return; //if no results were returned, exit... //Accessibility: render the list of photos $text = '
      '; foreach ($photos as $photo) { $text .= '
    • '; $text .= ''.s($photo['title']).'
    • \n"; } $text .= "
    \n"; $this->content = new stdClass; $this->content->text = $text; $this->content->footer = ''; return $this->content; } function fetch_request($request){ $c = new curl(array('cache' => true, 'module_cache'=> 'tag_flickr')); // Set custom user agent as Flickr blocks our "MoodleBot" agent string. $c->setopt([ 'CURLOPT_USERAGENT' => flickr_client::user_agent(), ]); $response = $c->get($request); return $response; } function build_photo_url($photo, $size='medium') { //receives an array (can use the individual photo data returned //from an API call) and returns a URL (doesn't mean that the //file size exists) $sizes = array( 'square' => '_s', 'thumbnail' => '_t', 'small' => '_m', 'medium' => '', 'large' => '_b', 'original' => '_o' ); $size = strtolower($size); if (!array_key_exists($size, $sizes)) { $size = 'medium'; } if ($size == 'original') { $url = 'http://farm' . $photo['farm'] . '.static.flickr.com/' . $photo['server'] . '/' . $photo['id'] . '_' . $photo['originalsecret'] . '_o' . '.' . $photo['originalformat']; } else { $url = 'http://farm' . $photo['farm'] . '.static.flickr.com/' . $photo['server'] . '/' . $photo['id'] . '_' . $photo['secret'] . $sizes[$size] . '.jpg'; } return $url; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = !empty($this->config) ? $this->config : new stdClass(); return (object) [ 'instance' => $configs, 'plugin' => new stdClass(), ]; } } PK'9\҅'tag_flickr/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_tag_flickr. * * @package block_tag_flickr * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_tag_flickr\privacy; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\context; use core_privacy\local\request\contextlist; use core_privacy\local\request\userlist; use core_privacy\local\request\approved_userlist; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_tag_flickr implementing metadata and plugin provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { $collection->add_external_location_link( 'flickr.com', [ 'tags' => 'privacy:metadata:block_tag_flickr:tags' ], 'privacy:metadata:block_tag_flickr' ); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { return new contextlist(); } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { } /** * Delete all data for all users in the specified context. * * @param \context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(\context $context) { } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { } } PK'9\$E;tag_flickr/tests/behat/configuring_tag_flickr_block.featurenu[@block @block_tag_flickr Feature: Adding and configuring Flickr block In order to have the Flickr block used As a admin I need to add the Flickr block to the tags site page Background: # We need to create a user to use tag here because the tags site page only shows the tag that currently being used. Given the following "users" exist: | username | interests | | student1 | Cats | And I log in as "admin" And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | tag_flickr | System | 1 | tag-search | side-post | | tag_flickr | System | 1 | tag-index | side-post | # TODO MDL-57120 site "Tags" link not accessible without navigation block. And I add the "Navigation" block if not present And I click on "Site pages" "list_item" in the "Navigation" "block" And I click on "Tags" "link" in the "Navigation" "block" @javascript Scenario: Adding Flickr block to the tags site page Given "block_tag_flickr" "block" should exist When I configure the "Flickr" block Then I should see "Flickr block title" And I set the field "Flickr block title" to "The Flickr block header" And I press "Save changes" And "block_tag_flickr" "block" should exist And "The Flickr block header" "block" should exist @javascript Scenario: Adding Flickr block to a specific tag page Given I click on "Cats" "link" Then "block_tag_flickr" "block" should exist And ".flickr-photos" "css_element" should exist in the "block_tag_flickr" "block" PK'9\G_S_S edit_form.phpnu[. /** * Defines the base class form used by blocks/edit.php to edit block instance configuration. * * It works with the {@see block_edit_form} class, or rather the particular * subclass defined by this block, to do the editing. * * @package core_block * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once($CFG->libdir . '/blocklib.php'); /** * The base class form used by blocks/edit.php to edit block instance configuration. * * @property-read block_base $block * @property-read moodle_page $page * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_edit_form extends \core_form\dynamic_form { /** * The block instance we are editing. * @var block_base */ private $_block; /** * The page we are editing this block in association with. * @var moodle_page */ private $_page; /** * Defaults set in set_data() that need to be returned in get_data() if form elements were not created * @var array */ protected $defaults = []; /** * Magic getter for backward compatibility * * @param string $name * @return block_base|moodle_page */ public function __get(string $name) { if ($name === 'page') { return $this->get_page(); } else if ($name === 'block') { return $this->get_block(); } else { throw new coding_exception('Property '.$name.' does not exist'); } } /** * Page where we are adding or editing the block * * To access you can also use magic property $this->page * * @return moodle_page * @throws moodle_exception */ protected function get_page(): moodle_page { if (!$this->_page && !empty($this->_customdata['page'])) { $this->_page = $this->_customdata['page']; } else if (!$this->_page) { if (!$pagehash = $this->optional_param('pagehash', '', PARAM_ALPHANUMEXT)) { throw new \moodle_exception('missingparam', '', '', 'pagehash'); } $this->_page = moodle_page::retrieve_edited_page($pagehash, MUST_EXIST); $this->_page->blocks->load_blocks(); } return $this->_page; } /** * Instance of the block that is being added or edited * * To access you can also use magic property $this->block * * If {{@see self::display_form_when_adding()}} returns true and the configuration * form is displayed when adding block, the $this->block->id will be null. * * @return block_base * @throws block_not_on_page_exception * @throws moodle_exception */ protected function get_block(): block_base { if (!$this->_block && !empty($this->_customdata['block'])) { $this->_block = $this->_customdata['block']; } else if (!$this->_block) { $blockid = $this->optional_param('blockid', null, PARAM_INT); $blockname = $this->optional_param('blockname', null, PARAM_PLUGIN); if ($blockname && !$blockid) { $this->_block = block_instance($blockname); $this->_block->page = $this->page; $this->_block->context = $this->page->context; $this->_block->instance = (object)['parentcontextid' => $this->page->context->id, 'id' => null]; } else { $this->_block = $this->page->blocks->find_instance($blockid); } } return $this->_block; } /** * Form definition */ function definition() { $mform =& $this->_form; $mform->addElement('hidden', 'blockid', $this->block->instance->id); $mform->setType('blockid', PARAM_INT); $mform->addElement('hidden', 'blockname', $this->optional_param('blockname', null, PARAM_PLUGIN)); $mform->setType('blockname', PARAM_PLUGIN); $mform->addElement('hidden', 'blockregion', $this->optional_param('blockregion', null, PARAM_TEXT)); $mform->setType('blockregion', PARAM_TEXT); $mform->addElement('hidden', 'pagehash', $this->optional_param('pagehash', null, PARAM_ALPHANUMEXT)); $mform->setType('pagehash', PARAM_ALPHANUMEXT); // First show fields specific to this type of block. $this->specific_definition($mform); if (!$this->block->instance->id) { return; } // Then show the fields about where this block appears. $mform->addElement('header', 'whereheader', get_string('wherethisblockappears', 'block')); // If the current weight of the block is out-of-range, add that option in. $blockweight = $this->block->instance->weight; $weightoptions = array(); if ($blockweight < -block_manager::MAX_WEIGHT) { $weightoptions[$blockweight] = $blockweight; } for ($i = -block_manager::MAX_WEIGHT; $i <= block_manager::MAX_WEIGHT; $i++) { $weightoptions[$i] = $i; } if ($blockweight > block_manager::MAX_WEIGHT) { $weightoptions[$blockweight] = $blockweight; } $first = reset($weightoptions); $weightoptions[$first] = get_string('bracketfirst', 'block', $first); $last = end($weightoptions); $weightoptions[$last] = get_string('bracketlast', 'block', $last); $regionoptions = $this->page->theme->get_all_block_regions(); foreach ($this->page->blocks->get_regions() as $region) { // Make sure to add all custom regions of this particular page too. if (!isset($regionoptions[$region])) { $regionoptions[$region] = $region; } } $parentcontext = context::instance_by_id($this->block->instance->parentcontextid); $mform->addElement('static', 'bui_homecontext', get_string('createdat', 'block'), $parentcontext->get_context_name()); $mform->addHelpButton('bui_homecontext', 'createdat', 'block'); // For pre-calculated (fixed) pagetype lists $pagetypelist = array(); // parse pagetype patterns $bits = explode('-', $this->page->pagetype); // First of all, check if we are editing blocks @ front-page or no and // make some dark magic if so (MDL-30340) because each page context // implies one (and only one) harcoded page-type that will be set later // when processing the form data at {@see block_manager::process_url_edit()}. // Front page, show the page-contexts element and set $pagetypelist to 'any page' (*) // as unique option. Processign the form will do any change if needed. if ($this->is_editing_the_frontpage()) { $contextoptions = array(); $contextoptions[BUI_CONTEXTS_FRONTPAGE_ONLY] = get_string('showonfrontpageonly', 'block'); $contextoptions[BUI_CONTEXTS_FRONTPAGE_SUBS] = get_string('showonfrontpageandsubs', 'block'); $contextoptions[BUI_CONTEXTS_ENTIRE_SITE] = get_string('showonentiresite', 'block'); $mform->addElement('select', 'bui_contexts', get_string('contexts', 'block'), $contextoptions); $mform->addHelpButton('bui_contexts', 'contexts', 'block'); $pagetypelist['*'] = '*'; // This is not going to be shown ever, it's an unique option // Any other system context block, hide the page-contexts element, // it's always system-wide BUI_CONTEXTS_ENTIRE_SITE } else if ($parentcontext->contextlevel == CONTEXT_SYSTEM) { } else if ($parentcontext->contextlevel == CONTEXT_COURSE) { // 0 means display on current context only, not child contexts // but if course managers select mod-* as pagetype patterns, block system will overwrite this option // to 1 (display on current context and child contexts) } else if ($parentcontext->contextlevel == CONTEXT_MODULE or $parentcontext->contextlevel == CONTEXT_USER) { // module context doesn't have child contexts, so display in current context only } else { $parentcontextname = $parentcontext->get_context_name(); $contextoptions[BUI_CONTEXTS_CURRENT] = get_string('showoncontextonly', 'block', $parentcontextname); $contextoptions[BUI_CONTEXTS_CURRENT_SUBS] = get_string('showoncontextandsubs', 'block', $parentcontextname); $mform->addElement('select', 'bui_contexts', get_string('contexts', 'block'), $contextoptions); } $mform->setType('bui_contexts', PARAM_INT); // Generate pagetype patterns by callbacks if necessary (has not been set specifically) if (empty($pagetypelist)) { $pagetypelist = generate_page_type_patterns($this->page->pagetype, $parentcontext, $this->page->context); $displaypagetypewarning = false; if (!array_key_exists($this->block->instance->pagetypepattern, $pagetypelist)) { // Pushing block's existing page type pattern $pagetypestringname = 'page-'.str_replace('*', 'x', $this->block->instance->pagetypepattern); if (get_string_manager()->string_exists($pagetypestringname, 'pagetype')) { $pagetypelist[$this->block->instance->pagetypepattern] = get_string($pagetypestringname, 'pagetype'); } else { //as a last resort we could put the page type pattern in the select box //however this causes mod-data-view to be added if the only option available is mod-data-* // so we are just showing a warning to users about their prev setting being reset $displaypagetypewarning = true; } } } // hide page type pattern select box if there is only one choice if (count($pagetypelist) > 1) { if ($displaypagetypewarning) { $mform->addElement('static', 'pagetypewarning', '', get_string('pagetypewarning','block')); } $mform->addElement('select', 'bui_pagetypepattern', get_string('restrictpagetypes', 'block'), $pagetypelist); } else { $values = array_keys($pagetypelist); $value = array_pop($values); // Now we are really hiding a lot (both page-contexts and page-type-patterns), // specially in some systemcontext pages having only one option (my/user...) // so, until it's decided if we are going to add the 'bring-back' pattern to // all those pages or no (see MDL-30574), we are going to show the unique // element statically // TODO: Revisit this once MDL-30574 has been decided and implemented, although // perhaps it's not bad to always show this statically when only one pattern is // available. if (!$this->is_editing_the_frontpage()) { // Try to beautify it $strvalue = $value; $strkey = 'page-'.str_replace('*', 'x', $strvalue); if (get_string_manager()->string_exists($strkey, 'pagetype')) { $strvalue = get_string($strkey, 'pagetype'); } // Show as static (hidden has been set already) $mform->addElement('static', 'bui_staticpagetypepattern', get_string('restrictpagetypes','block'), $strvalue); } } if ($this->page->subpage) { if ($parentcontext->contextlevel != CONTEXT_USER) { $subpageoptions = array( '%@NULL@%' => get_string('anypagematchingtheabove', 'block'), $this->page->subpage => get_string('thisspecificpage', 'block', $this->page->subpage), ); $mform->addElement('select', 'bui_subpagepattern', get_string('subpages', 'block'), $subpageoptions); } } $defaultregionoptions = $regionoptions; $defaultregion = $this->block->instance->defaultregion; if (!array_key_exists($defaultregion, $defaultregionoptions)) { $defaultregionoptions[$defaultregion] = $defaultregion; } $mform->addElement('select', 'bui_defaultregion', get_string('defaultregion', 'block'), $defaultregionoptions); $mform->addHelpButton('bui_defaultregion', 'defaultregion', 'block'); $mform->addElement('select', 'bui_defaultweight', get_string('defaultweight', 'block'), $weightoptions); $mform->addHelpButton('bui_defaultweight', 'defaultweight', 'block'); // Where this block is positioned on this page. $mform->addElement('header', 'onthispage', get_string('onthispage', 'block')); $mform->addElement('selectyesno', 'bui_visible', get_string('visible', 'block')); $blockregion = $this->block->instance->region; if (!array_key_exists($blockregion, $regionoptions)) { $regionoptions[$blockregion] = $blockregion; } $mform->addElement('select', 'bui_region', get_string('region', 'block'), $regionoptions); $mform->addElement('select', 'bui_weight', get_string('weight', 'block'), $weightoptions); $pagefields = array('bui_visible', 'bui_region', 'bui_weight'); if (!$this->block->user_can_edit()) { $mform->hardFreezeAllVisibleExcept($pagefields); } if (!$this->page->user_can_edit_blocks()) { $mform->hardFreeze($pagefields); } if (!empty($this->_customdata['actionbuttons'])) { $this->add_action_buttons(); } } /** * Returns true if the user is editing a frontpage. * @return bool */ public function is_editing_the_frontpage() { // There are some conditions to check related to contexts. $ctxconditions = $this->page->context->contextlevel == CONTEXT_COURSE && $this->page->context->instanceid == get_site()->id; $issiteindex = (strpos($this->page->pagetype, 'site-index') === 0); // So now we can be 100% sure if edition is happening at frontpage. return ($ctxconditions && $issiteindex); } /** * Prepare block configuration data and add default values when needed * * @param stdClass $defaults * @return stdClass */ protected function prepare_defaults(stdClass $defaults): stdClass { // Prefix bui_ on all the core field names. $blockfields = array('showinsubcontexts', 'pagetypepattern', 'subpagepattern', 'parentcontextid', 'defaultregion', 'defaultweight', 'visible', 'region', 'weight'); foreach ($blockfields as $field) { $newname = 'bui_' . $field; $defaults->$newname = $defaults->$field ?? null; } // Copy block config into config_ fields. if (!empty($this->block->config)) { foreach ($this->block->config as $field => $value) { $configfield = 'config_' . $field; $defaults->$configfield = $value; } } // Munge ->subpagepattern becuase HTML selects don't play nicely with NULLs. if (empty($defaults->bui_subpagepattern)) { $defaults->bui_subpagepattern = '%@NULL@%'; } $systemcontext = context_system::instance(); if ($defaults->parentcontextid == $systemcontext->id) { $defaults->bui_contexts = BUI_CONTEXTS_ENTIRE_SITE; // System-wide and sticky } else { $defaults->bui_contexts = $defaults->bui_showinsubcontexts; } // Some fields may not be editable, remember the values here so we can return them in get_data(). $this->defaults = [ 'bui_parentcontextid' => $defaults->bui_parentcontextid, 'bui_contexts' => $defaults->bui_contexts, 'bui_pagetypepattern' => $defaults->bui_pagetypepattern, 'bui_subpagepattern' => $defaults->bui_subpagepattern, ]; return $defaults; } /** * Load in existing data as form defaults * * @param stdClass $defaults * @return void */ public function set_data($defaults) { parent::set_data($this->prepare_defaults($defaults)); } /** * Override this to create any form fields specific to this type of block. * @param \MoodleQuickForm $mform the form being built. */ protected function specific_definition($mform) { // By default, do nothing. } /** * Return submitted data if properly submitted or returns NULL if validation fails or * if there is no submitted data. * * @return stdClass submitted data; NULL if not valid or not submitted or cancelled */ public function get_data() { if ($data = parent::get_data()) { // Blocklib expects 'bui_editingatfrontpage' property to be returned from this form. $data->bui_editingatfrontpage = $this->is_editing_the_frontpage(); // Some fields are non-editable and we need to populate them with the values from set_data(). return (object)((array)$data + $this->defaults); } return $data; } /** * Returns context where this form is used * * @return context */ protected function get_context_for_dynamic_submission(): context { return $this->page->context; } /** * Checks if current user has access to this form, otherwise throws exception */ protected function check_access_for_dynamic_submission(): void { if ($this->block->instance->id) { if (!$this->page->user_can_edit_blocks() && !$this->block->user_can_edit()) { throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock')); } } else { if (!$this->page->user_can_edit_blocks()) { throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('addblock')); } $addableblocks = $this->page->blocks->get_addable_blocks(); $blocktype = $this->block->name(); if (!array_key_exists($blocktype, $addableblocks)) { throw new moodle_exception('cannotaddthisblocktype', '', $this->page->url->out(), $blocktype); } } } /** * Process the form submission, used if form was submitted via AJAX */ public function process_dynamic_submission() { if ($this->block->instance->id) { $this->page->blocks->save_block_data($this->block, $this->get_data()); } else { $blockregion = $this->optional_param('blockregion', null, PARAM_TEXT); $newblock = $this->page->blocks->add_block_at_end_of_default_region($this->block->name(), empty($blockregion) ? null : $blockregion); if (empty($newblock)) { return; } $this->page->blocks->load_blocks(); $newblock = $this->page->blocks->find_instance($newblock->instance->id); $newdata = $this->prepare_defaults($newblock->instance); foreach ($this->get_data() as $key => $value) { $newdata->$key = $value; } $this->page->blocks->save_block_data($newblock, $newdata); } } /** * Load in existing data as form defaults */ public function set_data_for_dynamic_submission(): void { $this->set_data($this->block->instance); } /** * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX * * @return moodle_url */ protected function get_page_url_for_dynamic_submission(): moodle_url { return $this->page->url; } /** * Display the configuration form when block is being added to the page * * By default when block is added to the page it is added with the default configuration. * Some block may require configuration, for example, "glossary random entry" block * needs a glossary to be selected, "RSS feed" block needs an RSS feed to be selected, etc. * * Such blocks can override this function and return true. These blocks must * ensure that the function specific_definition() will work if there is no current block id. * * @return bool */ public static function display_form_when_adding(): bool { return false; } } PK'9\ekknavigation/styles.cssnu[.block_navigation .block_tree .depth_1 > .tree_item.branch { padding-left: 0; background-image: none; } .block_navigation .block_tree .depth_1 > ul { margin: 0; } .block_navigation .block_tree ul { margin-left: 18px; } .block_navigation .block_tree p.hasicon { text-indent: -21px; padding-left: 21px; } .block_navigation .block_tree p.hasicon img { width: 16px; height: 16px; margin-top: 3px; margin-right: 5px; vertical-align: top; } .block_navigation .block_tree p.hasicon.visibleifjs { display: block; } .block_navigation .block_tree .tree_item { cursor: pointer; padding-left: 0; margin: 3px 0; background-position: 0 50%; background-repeat: no-repeat; word-wrap: break-word; } .block_navigation .block_tree .tree_item.branch { padding-left: 21px; } .block_navigation .block_tree .active_tree_node { font-weight: bold; } .block_navigation .block_tree [aria-expanded="true"] { background-image: url('[[pix:t/expanded]]'); } .block_navigation .block_tree [aria-expanded="false"] { background-image: url('[[pix:t/collapsed]]'); } .block_navigation .block_tree [aria-expanded="true"].emptybranch { background-image: url('[[pix:t/collapsed_empty]]'); } .block_navigation .block_tree [aria-expanded="false"] p.loading { background-image: url('[[pix:i/loading_small]]'); } /*rtl:raw: .block_navigation .block_tree [aria-expanded="false"] {background-image: url('[[pix:t/collapsed_rtl]]');} .block_navigation .block_tree [aria-expanded="true"].emptybranch {background-image: url('[[pix:t/collapsed_empty_rtl]]');} .block_navigation .block_tree [aria-expanded="false"].loading {background-image: url('[[pix:i/loading_small]]');} */ .block_navigation .block_tree [aria-hidden="false"] { display: block; } .block_navigation .block_tree [aria-hidden="true"]:not(.icon) { display: none; } PK'9\ׂf!f!navigation/renderer.phpnu[. /** * Outputs the navigation tree. * * @since Moodle 2.0 * @package block_navigation * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Renderer for block navigation * * @package block_navigation * @category navigation * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_navigation_renderer extends plugin_renderer_base { /** * Returns the content of the navigation tree. * * @param global_navigation $navigation * @param int $expansionlimit * @param array $options * @return string $content */ public function navigation_tree(global_navigation $navigation, $expansionlimit, array $options = array()) { $navigation->add_class('navigation_node'); $navigationattrs = array( 'class' => 'block_tree list', 'role' => 'tree', 'data-ajax-loader' => 'block_navigation/nav_loader'); $content = $this->navigation_node(array($navigation), $navigationattrs, $expansionlimit, $options); if (isset($navigation->id) && !is_numeric($navigation->id) && !empty($content)) { $content = $this->output->box($content, 'block_tree_box', $navigation->id); } return $content; } /** * Produces a navigation node for the navigation tree * * @param navigation_node[] $items * @param array $attrs * @param int $expansionlimit * @param array $options * @param int $depth * @return string */ protected function navigation_node($items, $attrs=array(), $expansionlimit=null, array $options = array(), $depth=1) { // Exit if empty, we don't want an empty ul element. if (count($items) === 0) { return ''; } // Turn our navigation items into list items. $lis = array(); // Set the number to be static for unique id's. static $number = 0; $htmlidprefix = html_writer::random_id(); foreach ($items as $item) { $number++; if (!$item->display && !$item->contains_active_node()) { continue; } $isexpandable = (empty($expansionlimit) || ($item->type > navigation_node::TYPE_ACTIVITY || $item->type < $expansionlimit) || ($item->contains_active_node() && $item->children->count() > 0)); // Skip elements which have no content and no action - no point in showing them if (!$isexpandable && empty($item->action)) { continue; } $id = $item->id ? $item->id : html_writer::random_id(); $content = $item->get_content(); $title = $item->get_title(); $ulattr = ['id' => $id . '_group', 'role' => 'group']; $liattr = ['class' => [$item->get_css_type(), 'depth_'.$depth], 'role' => 'treeitem']; $pattr = ['class' => ['tree_item']]; $pattr += !empty($item->id) ? ['id' => $item->id] : []; $isbranch = $isexpandable && ($item->children->count() > 0 || ($item->has_children() && (isloggedin() || $item->type <= navigation_node::TYPE_CATEGORY))); $hasicon = ((!$isbranch || $item->type == navigation_node::TYPE_ACTIVITY || $item->type == navigation_node::TYPE_RESOURCE) && $item->icon instanceof renderable); $icon = ''; if ($hasicon) { $liattr['class'][] = 'item_with_icon'; $pattr['class'][] = 'hasicon'; $icon = $this->output->render($item->icon); // Because an icon is being used we're going to wrap the actual content in a span. // This will allow designers to create columns for the content, as we've done in styles.css. $content = $icon . html_writer::span($content, 'item-content-wrap'); } if ($item->helpbutton !== null) { $content = trim($item->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton')); } if (empty($content)) { continue; } $nodetextid = $htmlidprefix . '_label_' . $depth . '_' . $number; $attributes = array('tabindex' => '-1', 'id' => $nodetextid); if ($title !== '') { $attributes['title'] = $title; } if ($item->hidden) { $attributes['class'] = 'dimmed_text'; } if (is_string($item->action) || empty($item->action) || (($item->type === navigation_node::TYPE_CATEGORY || $item->type === navigation_node::TYPE_MY_CATEGORY) && empty($options['linkcategories']))) { $content = html_writer::tag('span', $content, $attributes); } else if ($item->action instanceof action_link) { //TODO: to be replaced with something else $link = $item->action; $link->text = $icon.html_writer::span($link->text, 'item-content-wrap'); $link->attributes = array_merge($link->attributes, $attributes); $content = $this->output->render($link); } else if ($item->action instanceof moodle_url) { $content = html_writer::link($item->action, $content, $attributes); } if ($isbranch) { $ariaexpanded = $item->has_children() && (!$item->forceopen || $item->collapse); $pattr['class'][] = 'branch'; $liattr['class'][] = 'contains_branch'; $liattr += ['aria-expanded' => $ariaexpanded ? "false" : "true"]; if ($item->requiresajaxloading) { $liattr += [ 'data-requires-ajax' => 'true', 'data-loaded' => 'false', 'data-node-id' => $item->id, 'data-node-key' => $item->key, 'data-node-type' => $item->type ]; } else { $liattr += ['aria-owns' => $id . '_group']; } } if ($item->isactive === true) { $liattr['class'][] = 'current_branch'; } if (!empty($item->classes) && count($item->classes)>0) { $pattr['class'] = array_merge($pattr['class'], $item->classes); } $liattr['class'] = join(' ', $liattr['class']); $pattr['class'] = join(' ', $pattr['class']); $liattr += $depth == 1 ? ['data-collapsible' => 'false'] : []; if (isset($liattr['aria-expanded']) && $liattr['aria-expanded'] === 'false') { $ulattr += ['aria-hidden' => 'true']; } // Create the structure. $content = html_writer::tag('p', $content, $pattr); if ($isexpandable) { $content .= $this->navigation_node($item->children, $ulattr, $expansionlimit, $options, $depth + 1); } if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) { $content = html_writer::empty_tag('hr') . $content; } $liattr['aria-labelledby'] = $nodetextid; $content = html_writer::tag('li', $content, $liattr); $lis[] = $content; } if (count($lis) === 0) { // There is still a chance, despite having items, that nothing had content and no list items were created. return ''; } // We used to separate using new lines, however we don't do that now, instead we'll save a few chars. // The source is complex already anyway. return html_writer::tag('ul', implode('', $lis), $attrs); } } PK'9\lnavigation/db/access.phpnu[. /** * Navigation block caps. * * @package block_navigation * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/navigation:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/navigation:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\{o< navigation/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the navigation block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @since Moodle 2.0 * @package block_navigation * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * As of the implementation of this block and the general navigation code * in Moodle 2.0 the body of immediate upgrade work for this block and * settings is done in core upgrade {@see lib/db/upgrade.php} * * There were several reasons that they were put there and not here, both becuase * the process for the two blocks was very similar and because the upgrade process * was complex due to us wanting to remvoe the outmoded blocks that this * block was going to replace. * * @param int $oldversion * @param object $block */ function xmldb_block_navigation_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\y'navigation/lang/en/block_navigation.phpnu[. /** * This file contains language strings used in the global navigation block * * @since Moodle 2.0 * @package block_navigation * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['everything'] = 'Everything'; $string['courses'] = 'Categories and courses'; $string['coursestructures'] = 'Categories, courses, and course structures'; $string['courseactivities'] = 'Categories, courses, and course activities'; $string['enabledockdesc'] = 'Allow the user to dock this block'; $string['expansionlimit'] = 'Generate navigation for the following'; $string['linkcategoriesdesc'] = 'Display categories as links'; $string['navigation:addinstance'] = 'Add a new navigation block'; $string['navigation:myaddinstance'] = 'Add a new navigation block to Dashboard'; $string['pluginname'] = 'Navigation'; $string['trimmode'] = 'Trim mode'; $string['trimmoderight'] = 'Trim characters from the right'; $string['trimmodeleft'] = 'Trim characters from the left'; $string['trimmodecenter'] = 'Trim characters from the center'; $string['trimlength'] = 'How many characters to trim to'; $string['privacy:metadata'] = 'The Navigation block only shows data stored in other locations.'; PK'9\p˻> > navigation/edit_form.phpnu[. /** * Form for editing global navigation instances. * * @since Moodle 2.0 * @package block_navigation * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Form for editing global navigation instances. * * @package block_navigation * @category navigation * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_navigation_edit_form extends block_edit_form { /** * @param MoodleQuickForm $mform */ protected function specific_definition($mform) { global $CFG; $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $mods = array('enabledock'=>'yes', 'linkcategories'=>'no'); $yesnooptions = array('yes'=>get_string('yes'), 'no'=>get_string('no')); foreach ($mods as $modname=>$default) { $mform->addElement('select', 'config_'.$modname, get_string($modname.'desc', $this->block->blockname), $yesnooptions); $mform->setDefault('config_'.$modname, $default); } $options = array( block_navigation::TRIM_RIGHT => get_string('trimmoderight', $this->block->blockname), block_navigation::TRIM_LEFT => get_string('trimmodeleft', $this->block->blockname), block_navigation::TRIM_CENTER => get_string('trimmodecenter', $this->block->blockname) ); $mform->addElement('select', 'config_trimmode', get_string('trimmode', $this->block->blockname), $options); $mform->setType('config_trimmode', PARAM_INT); $mform->addElement('text', 'config_trimlength', get_string('trimlength', $this->block->blockname)); $mform->setDefault('config_trimlength', 50); $mform->setType('config_trimlength', PARAM_INT); $options = array( 0 => get_string('everything', $this->block->blockname), global_navigation::TYPE_COURSE => get_string('courses', $this->block->blockname), global_navigation::TYPE_SECTION => get_string('coursestructures', $this->block->blockname), global_navigation::TYPE_ACTIVITY => get_string('courseactivities', $this->block->blockname) ); $mform->addElement('select', 'config_expansionlimit', get_string('expansionlimit', $this->block->blockname), $options); $mform->setType('config_expansionlimit', PARAM_INT); } }PK'9\#navigation/version.phpnu[. /** * Version details * * @package block_navigation * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_navigation'; // Full name of the plugin (used for diagnostics) PK'9\ 'navigation/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_navigation. * * @package block_navigation * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_navigation\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_navigation implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\řnavigation/amd/src/navblock.jsnu[// 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 . /** * Load the navigation tree javascript. * * @module block_navigation/navblock * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {notifyBlockContentUpdated} from 'core_block/events'; import Tree from 'core/tree'; /** * Initialise the navblock javascript for the specified block instance. * * @method * @param {Number} instanceId */ export const init = instanceId => { const navTree = new Tree(".block_navigation .block_tree"); const blockNode = document.querySelector(`[data-instance-id="${instanceId}"]`); /** * The method to call when then the navtree finishes expanding a group. * * @method finishExpandingGroup * @param {Object} item * @fires event:blockContentUpdated */ navTree.finishExpandingGroup = item => { Tree.prototype.finishExpandingGroup.call(navTree, item); notifyBlockContentUpdated(blockNode); }; /** * The method to call whe then the navtree collapses a group * * @method collapseGroup * @param {Object} item * @fires event:blockContentUpdated */ navTree.collapseGroup = item => { Tree.prototype.collapseGroup.call(navTree, item); notifyBlockContentUpdated(blockNode); }; }; PK'9\f[ɟ navigation/amd/src/nav_loader.jsnu[// 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 . /** * Load the nav tree items via ajax and render the response. * * @module block_navigation/nav_loader * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery', 'core/ajax', 'core/config', 'block_navigation/ajax_response_renderer'], function($, ajax, config, renderer) { var URL = config.wwwroot + '/lib/ajax/getnavbranch.php'; /** * Get the block instance id. * * @function getBlockInstanceId * @param {Element} element * @returns {String} the instance id */ function getBlockInstanceId(element) { return element.closest('[data-block]').attr('data-instanceid'); } return { load: function(element) { element = $(element); var promise = $.Deferred(); var data = { elementid: element.attr('data-node-id'), id: element.attr('data-node-key'), type: element.attr('data-node-type'), sesskey: config.sesskey, instance: getBlockInstanceId(element) }; var settings = { type: 'POST', dataType: 'json', data: data }; $.ajax(URL, settings).done(function(nodes) { renderer.render(element, nodes); promise.resolve(); }); return promise; } }; }); PK'9\XY'navigation/amd/src/site_admin_loader.jsnu[// 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 . /** * Load the site admin nav tree via ajax and render the response. * * @module block_navigation/site_admin_loader * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery', 'core/ajax', 'core/config', 'block_navigation/ajax_response_renderer'], function($, ajax, config, renderer) { var SITE_ADMIN_NODE_TYPE = 71; var URL = config.wwwroot + '/lib/ajax/getsiteadminbranch.php'; return { load: function(element) { element = $(element); var promise = $.Deferred(); var data = { type: SITE_ADMIN_NODE_TYPE, sesskey: config.sesskey }; var settings = { type: 'POST', dataType: 'json', data: data }; $.ajax(URL, settings).done(function(nodes) { renderer.render(element, nodes); promise.resolve(); }); return promise; } }; }); PK'9\4,navigation/amd/src/ajax_response_renderer.jsnu[// 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 . /** * Parse the response from the navblock ajax page and render the correct DOM * structure for the tree from it. * * @module block_navigation/ajax_response_renderer * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define([ 'jquery', 'core/templates', 'core/notification', 'core/url', 'core/aria', ], function( $, Templates, Notification, Url, Aria ) { // Mappings for the different types of nodes coming from the navigation. // Copied from lib/navigationlib.php navigation_node constants. var NODETYPE = { // @type int Activity (course module) = 40. ACTIVITY: 40, // @type int Resource (course module = 50. RESOURCE: 50, }; /** * Build DOM. * * @method buildDOM * @param {Object} rootElement the root element of DOM. * @param {object} nodes jquery object representing the nodes to be build. */ function buildDOM(rootElement, nodes) { var ul = $('
      '); ul.attr('role', 'group'); Aria.hide(ul); $.each(nodes, function(index, node) { if (typeof node !== 'object') { return; } var li = $('
    • '); var p = $('

      '); var id = node.id || node.key + '_tree_item'; var icon = null; var isBranch = (node.expandable || node.haschildren) ? true : false; li.attr('role', 'treeitem'); p.addClass('tree_item'); p.attr('id', id); // Negative tab index to allow it to receive focus. p.attr('tabindex', '-1'); if (node.requiresajaxloading) { li.attr('data-requires-ajax', true); li.attr('data-node-id', node.id); li.attr('data-node-key', node.key); li.attr('data-node-type', node.type); } if (isBranch) { li.addClass('collapsed contains_branch'); li.attr('aria-expanded', false); p.addClass('branch'); } var eleToAddIcon = null; if (node.link) { var link = $(''); eleToAddIcon = link; link.append('' + node.name + ''); if (node.hidden) { link.addClass('dimmed'); } p.append(link); } else { var span = $(''); eleToAddIcon = span; span.append('' + node.name + ''); if (node.hidden) { span.addClass('dimmed'); } p.append(span); } if (node.icon && (!isBranch || node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE)) { li.addClass('item_with_icon'); p.addClass('hasicon'); if (node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE) { icon = $(''); icon.attr('alt', node.icon.alt); icon.attr('title', node.icon.title); icon.attr('src', Url.imageUrl(node.icon.pix, node.icon.component)); $.each(node.icon.classes, function(index, className) { icon.addClass(className); }); eleToAddIcon.prepend(icon); } else { if (node.icon.component == 'moodle') { node.icon.component = 'core'; } Templates.renderPix(node.icon.pix, node.icon.component, node.icon.title).then(function(html) { // Prepend. eleToAddIcon.prepend(html); return; }).catch(Notification.exception); } } li.append(p); ul.append(li); if (node.children && node.children.length) { buildDOM(li, node.children); } else if (isBranch && !node.requiresajaxloading) { li.removeClass('contains_branch'); p.addClass('emptybranch'); } }); rootElement.append(ul); var id = rootElement.attr('id') + '_group'; ul.attr('id', id); rootElement.attr('aria-owns', id); rootElement.attr('role', 'treeitem'); } return { render: function(element, nodes) { // The first element of the response is the existing node so we start with processing the children. if (nodes.children && nodes.children.length) { buildDOM(element, nodes.children); var item = element.children("[role='treeitem']").first(); var group = element.find('#' + item.attr('aria-owns')); item.attr('aria-expanded', true); Aria.unhide(group); } else { if (element.hasClass('contains_branch')) { element.removeClass('contains_branch'); element.addClass('emptybranch'); } } } }; }); PK'9\L  -navigation/amd/build/site_admin_loader.min.jsnu[/** * Load the site admin nav tree via ajax and render the response. * * @module block_navigation/site_admin_loader * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_navigation/site_admin_loader",["jquery","core/ajax","core/config","block_navigation/ajax_response_renderer"],(function($,ajax,config,renderer){var URL=config.wwwroot+"/lib/ajax/getsiteadminbranch.php";return{load:function(element){element=$(element);var promise=$.Deferred(),settings={type:"POST",dataType:"json",data:{type:71,sesskey:config.sesskey}};return $.ajax(URL,settings).done((function(nodes){renderer.render(element,nodes),promise.resolve()})),promise}}})); //# sourceMappingURL=site_admin_loader.min.js.mapPK'9\&T (navigation/amd/build/navblock.min.js.mapnu[{"version":3,"file":"navblock.min.js","sources":["../src/navblock.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Load the navigation tree javascript.\n *\n * @module block_navigation/navblock\n * @copyright 2015 John Okely \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {notifyBlockContentUpdated} from 'core_block/events';\nimport Tree from 'core/tree';\n\n/**\n * Initialise the navblock javascript for the specified block instance.\n *\n * @method\n * @param {Number} instanceId\n */\nexport const init = instanceId => {\n const navTree = new Tree(\".block_navigation .block_tree\");\n const blockNode = document.querySelector(`[data-instance-id=\"${instanceId}\"]`);\n\n /**\n * The method to call when then the navtree finishes expanding a group.\n *\n * @method finishExpandingGroup\n * @param {Object} item\n * @fires event:blockContentUpdated\n */\n navTree.finishExpandingGroup = item => {\n Tree.prototype.finishExpandingGroup.call(navTree, item);\n notifyBlockContentUpdated(blockNode);\n };\n\n /**\n * The method to call whe then the navtree collapses a group\n *\n * @method collapseGroup\n * @param {Object} item\n * @fires event:blockContentUpdated\n */\n navTree.collapseGroup = item => {\n Tree.prototype.collapseGroup.call(navTree, item);\n notifyBlockContentUpdated(blockNode);\n };\n};\n"],"names":["instanceId","navTree","Tree","blockNode","document","querySelector","finishExpandingGroup","item","prototype","call","collapseGroup"],"mappings":";;;;;;;oJA+BoBA,mBACVC,QAAU,IAAIC,cAAK,iCACnBC,UAAYC,SAASC,2CAAoCL,kBAS/DC,QAAQK,qBAAuBC,qBACtBC,UAAUF,qBAAqBG,KAAKR,QAASM,4CACxBJ,YAU9BF,QAAQS,cAAgBH,qBACfC,UAAUE,cAAcD,KAAKR,QAASM,4CACjBJ"}PK'9\ 2navigation/amd/build/ajax_response_renderer.min.jsnu[/** * Parse the response from the navblock ajax page and render the correct DOM * structure for the tree from it. * * @module block_navigation/ajax_response_renderer * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_navigation/ajax_response_renderer",["jquery","core/templates","core/notification","core/url","core/aria"],(function($,Templates,Notification,Url,Aria){var NODETYPE_ACTIVITY=40,NODETYPE_RESOURCE=50;function buildDOM(rootElement,nodes){var ul=$("
        ");ul.attr("role","group"),Aria.hide(ul),$.each(nodes,(function(index,node){if("object"==typeof node){var li=$("
      • "),p=$("

        "),id=node.id||node.key+"_tree_item",icon=null,isBranch=!(!node.expandable&&!node.haschildren);li.attr("role","treeitem"),p.addClass("tree_item"),p.attr("id",id),p.attr("tabindex","-1"),node.requiresajaxloading&&(li.attr("data-requires-ajax",!0),li.attr("data-node-id",node.id),li.attr("data-node-key",node.key),li.attr("data-node-type",node.type)),isBranch&&(li.addClass("collapsed contains_branch"),li.attr("aria-expanded",!1),p.addClass("branch"));var eleToAddIcon=null;if(node.link){var link=$('');eleToAddIcon=link,link.append(''+node.name+""),node.hidden&&link.addClass("dimmed"),p.append(link)}else{var span=$("");eleToAddIcon=span,span.append(''+node.name+""),node.hidden&&span.addClass("dimmed"),p.append(span)}!node.icon||isBranch&&node.type!==NODETYPE_ACTIVITY&&node.type!==NODETYPE_RESOURCE||(li.addClass("item_with_icon"),p.addClass("hasicon"),node.type===NODETYPE_ACTIVITY||node.type===NODETYPE_RESOURCE?((icon=$("")).attr("alt",node.icon.alt),icon.attr("title",node.icon.title),icon.attr("src",Url.imageUrl(node.icon.pix,node.icon.component)),$.each(node.icon.classes,(function(index,className){icon.addClass(className)})),eleToAddIcon.prepend(icon)):("moodle"==node.icon.component&&(node.icon.component="core"),Templates.renderPix(node.icon.pix,node.icon.component,node.icon.title).then((function(html){eleToAddIcon.prepend(html)})).catch(Notification.exception))),li.append(p),ul.append(li),node.children&&node.children.length?buildDOM(li,node.children):isBranch&&!node.requiresajaxloading&&(li.removeClass("contains_branch"),p.addClass("emptybranch"))}})),rootElement.append(ul);var id=rootElement.attr("id")+"_group";ul.attr("id",id),rootElement.attr("aria-owns",id),rootElement.attr("role","treeitem")}return{render:function(element,nodes){if(nodes.children&&nodes.children.length){buildDOM(element,nodes.children);var item=element.children("[role='treeitem']").first(),group=element.find("#"+item.attr("aria-owns"));item.attr("aria-expanded",!0),Aria.unhide(group)}else element.hasClass("contains_branch")&&(element.removeClass("contains_branch"),element.addClass("emptybranch"))}}})); //# sourceMappingURL=ajax_response_renderer.min.js.mapPK'9\c%O#O#6navigation/amd/build/ajax_response_renderer.min.js.mapnu[{"version":3,"file":"ajax_response_renderer.min.js","sources":["../src/ajax_response_renderer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Parse the response from the navblock ajax page and render the correct DOM\n * structure for the tree from it.\n *\n * @module block_navigation/ajax_response_renderer\n * @copyright 2015 John Okely \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/templates',\n 'core/notification',\n 'core/url',\n 'core/aria',\n], function(\n $,\n Templates,\n Notification,\n Url,\n Aria\n) {\n\n // Mappings for the different types of nodes coming from the navigation.\n // Copied from lib/navigationlib.php navigation_node constants.\n var NODETYPE = {\n // @type int Activity (course module) = 40.\n ACTIVITY: 40,\n // @type int Resource (course module = 50.\n RESOURCE: 50,\n };\n\n /**\n * Build DOM.\n *\n * @method buildDOM\n * @param {Object} rootElement the root element of DOM.\n * @param {object} nodes jquery object representing the nodes to be build.\n */\n function buildDOM(rootElement, nodes) {\n var ul = $('
          ');\n ul.attr('role', 'group');\n Aria.hide(ul);\n\n $.each(nodes, function(index, node) {\n if (typeof node !== 'object') {\n return;\n }\n\n var li = $('
        • ');\n var p = $('

          ');\n var id = node.id || node.key + '_tree_item';\n var icon = null;\n var isBranch = (node.expandable || node.haschildren) ? true : false;\n\n li.attr('role', 'treeitem');\n p.addClass('tree_item');\n p.attr('id', id);\n // Negative tab index to allow it to receive focus.\n p.attr('tabindex', '-1');\n\n if (node.requiresajaxloading) {\n li.attr('data-requires-ajax', true);\n li.attr('data-node-id', node.id);\n li.attr('data-node-key', node.key);\n li.attr('data-node-type', node.type);\n }\n\n if (isBranch) {\n li.addClass('collapsed contains_branch');\n li.attr('aria-expanded', false);\n p.addClass('branch');\n }\n\n var eleToAddIcon = null;\n if (node.link) {\n var link = $('');\n\n eleToAddIcon = link;\n link.append('' + node.name + '');\n\n if (node.hidden) {\n link.addClass('dimmed');\n }\n\n p.append(link);\n } else {\n var span = $('');\n\n eleToAddIcon = span;\n span.append('' + node.name + '');\n\n if (node.hidden) {\n span.addClass('dimmed');\n }\n\n p.append(span);\n }\n\n if (node.icon && (!isBranch || node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE)) {\n li.addClass('item_with_icon');\n p.addClass('hasicon');\n\n if (node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE) {\n icon = $('');\n icon.attr('alt', node.icon.alt);\n icon.attr('title', node.icon.title);\n icon.attr('src', Url.imageUrl(node.icon.pix, node.icon.component));\n $.each(node.icon.classes, function(index, className) {\n icon.addClass(className);\n });\n eleToAddIcon.prepend(icon);\n } else {\n if (node.icon.component == 'moodle') {\n node.icon.component = 'core';\n }\n Templates.renderPix(node.icon.pix, node.icon.component, node.icon.title).then(function(html) {\n // Prepend.\n eleToAddIcon.prepend(html);\n return;\n }).catch(Notification.exception);\n }\n }\n\n li.append(p);\n ul.append(li);\n\n if (node.children && node.children.length) {\n buildDOM(li, node.children);\n } else if (isBranch && !node.requiresajaxloading) {\n li.removeClass('contains_branch');\n p.addClass('emptybranch');\n }\n });\n\n rootElement.append(ul);\n var id = rootElement.attr('id') + '_group';\n ul.attr('id', id);\n rootElement.attr('aria-owns', id);\n rootElement.attr('role', 'treeitem');\n }\n\n return {\n render: function(element, nodes) {\n // The first element of the response is the existing node so we start with processing the children.\n if (nodes.children && nodes.children.length) {\n buildDOM(element, nodes.children);\n\n var item = element.children(\"[role='treeitem']\").first();\n var group = element.find('#' + item.attr('aria-owns'));\n\n item.attr('aria-expanded', true);\n Aria.unhide(group);\n } else {\n if (element.hasClass('contains_branch')) {\n element.removeClass('contains_branch');\n element.addClass('emptybranch');\n }\n }\n }\n };\n});\n"],"names":["define","$","Templates","Notification","Url","Aria","NODETYPE","buildDOM","rootElement","nodes","ul","attr","hide","each","index","node","li","p","id","key","icon","isBranch","expandable","haschildren","addClass","requiresajaxloading","type","eleToAddIcon","link","title","append","name","hidden","span","alt","imageUrl","pix","component","classes","className","prepend","renderPix","then","html","catch","exception","children","length","removeClass","render","element","item","first","group","find","unhide","hasClass"],"mappings":";;;;;;;;AAuBAA,iDAAO,CACH,SACA,iBACA,oBACA,WACA,cACD,SACCC,EACAC,UACAC,aACAC,IACAC,UAKIC,kBAEU,GAFVA,kBAIU,YAULC,SAASC,YAAaC,WACvBC,GAAKT,EAAE,aACXS,GAAGC,KAAK,OAAQ,SAChBN,KAAKO,KAAKF,IAEVT,EAAEY,KAAKJ,OAAO,SAASK,MAAOC,SACN,iBAATA,UAIPC,GAAKf,EAAE,aACPgB,EAAIhB,EAAE,WACNiB,GAAKH,KAAKG,IAAMH,KAAKI,IAAM,aAC3BC,KAAO,KACPC,YAAYN,KAAKO,aAAcP,KAAKQ,aAExCP,GAAGL,KAAK,OAAQ,YAChBM,EAAEO,SAAS,aACXP,EAAEN,KAAK,KAAMO,IAEbD,EAAEN,KAAK,WAAY,MAEfI,KAAKU,sBACLT,GAAGL,KAAK,sBAAsB,GAC9BK,GAAGL,KAAK,eAAgBI,KAAKG,IAC7BF,GAAGL,KAAK,gBAAiBI,KAAKI,KAC9BH,GAAGL,KAAK,iBAAkBI,KAAKW,OAG/BL,WACAL,GAAGQ,SAAS,6BACZR,GAAGL,KAAK,iBAAiB,GACzBM,EAAEO,SAAS,eAGXG,aAAe,QACfZ,KAAKa,KAAM,KACPA,KAAO3B,EAAE,aAAec,KAAKc,MAAQ,WAAad,KAAKa,KAAO,UAElED,aAAeC,KACfA,KAAKE,OAAO,mCAAqCf,KAAKgB,KAAO,WAEzDhB,KAAKiB,QACLJ,KAAKJ,SAAS,UAGlBP,EAAEa,OAAOF,UACN,KACCK,KAAOhC,EAAE,iBAEb0B,aAAeM,KACfA,KAAKH,OAAO,mCAAqCf,KAAKgB,KAAO,WAEzDhB,KAAKiB,QACLC,KAAKT,SAAS,UAGlBP,EAAEa,OAAOG,OAGTlB,KAAKK,MAAUC,UAAYN,KAAKW,OAASpB,mBAAqBS,KAAKW,OAASpB,oBAC5EU,GAAGQ,SAAS,kBACZP,EAAEO,SAAS,WAEPT,KAAKW,OAASpB,mBAAqBS,KAAKW,OAASpB,oBACjDc,KAAOnB,EAAE,WACJU,KAAK,MAAOI,KAAKK,KAAKc,KAC3Bd,KAAKT,KAAK,QAASI,KAAKK,KAAKS,OAC7BT,KAAKT,KAAK,MAAOP,IAAI+B,SAASpB,KAAKK,KAAKgB,IAAKrB,KAAKK,KAAKiB,YACvDpC,EAAEY,KAAKE,KAAKK,KAAKkB,SAAS,SAASxB,MAAOyB,WACtCnB,KAAKI,SAASe,cAElBZ,aAAaa,QAAQpB,QAEM,UAAvBL,KAAKK,KAAKiB,YACVtB,KAAKK,KAAKiB,UAAY,QAE1BnC,UAAUuC,UAAU1B,KAAKK,KAAKgB,IAAKrB,KAAKK,KAAKiB,UAAWtB,KAAKK,KAAKS,OAAOa,MAAK,SAASC,MAEnFhB,aAAaa,QAAQG,SAEtBC,MAAMzC,aAAa0C,aAI9B7B,GAAGc,OAAOb,GACVP,GAAGoB,OAAOd,IAEND,KAAK+B,UAAY/B,KAAK+B,SAASC,OAC/BxC,SAASS,GAAID,KAAK+B,UACXzB,WAAaN,KAAKU,sBACzBT,GAAGgC,YAAY,mBACf/B,EAAEO,SAAS,oBAInBhB,YAAYsB,OAAOpB,QACfQ,GAAKV,YAAYG,KAAK,MAAQ,SAClCD,GAAGC,KAAK,KAAMO,IACdV,YAAYG,KAAK,YAAaO,IAC9BV,YAAYG,KAAK,OAAQ,kBAGtB,CACHsC,OAAQ,SAASC,QAASzC,UAElBA,MAAMqC,UAAYrC,MAAMqC,SAASC,OAAQ,CACzCxC,SAAS2C,QAASzC,MAAMqC,cAEpBK,KAAOD,QAAQJ,SAAS,qBAAqBM,QAC7CC,MAAQH,QAAQI,KAAK,IAAMH,KAAKxC,KAAK,cAEzCwC,KAAKxC,KAAK,iBAAiB,GAC3BN,KAAKkD,OAAOF,YAERH,QAAQM,SAAS,qBACjBN,QAAQF,YAAY,mBACpBE,QAAQ1B,SAAS"}PK'9\Ϳr *navigation/amd/build/nav_loader.min.js.mapnu[{"version":3,"file":"nav_loader.min.js","sources":["../src/nav_loader.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Load the nav tree items via ajax and render the response.\n *\n * @module block_navigation/nav_loader\n * @copyright 2015 John Okely \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/config', 'block_navigation/ajax_response_renderer'],\n function($, ajax, config, renderer) {\n var URL = config.wwwroot + '/lib/ajax/getnavbranch.php';\n\n /**\n * Get the block instance id.\n *\n * @function getBlockInstanceId\n * @param {Element} element\n * @returns {String} the instance id\n */\n function getBlockInstanceId(element) {\n return element.closest('[data-block]').attr('data-instanceid');\n }\n\n return {\n load: function(element) {\n element = $(element);\n var promise = $.Deferred();\n var data = {\n elementid: element.attr('data-node-id'),\n id: element.attr('data-node-key'),\n type: element.attr('data-node-type'),\n sesskey: config.sesskey,\n instance: getBlockInstanceId(element)\n };\n var settings = {\n type: 'POST',\n dataType: 'json',\n data: data\n };\n\n $.ajax(URL, settings).done(function(nodes) {\n renderer.render(element, nodes);\n promise.resolve();\n });\n\n return promise;\n }\n };\n});\n"],"names":["define","$","ajax","config","renderer","URL","wwwroot","getBlockInstanceId","element","closest","attr","load","promise","Deferred","settings","type","dataType","data","elementid","id","sesskey","instance","done","nodes","render","resolve"],"mappings":";;;;;;;AAsBAA,qCAAO,CAAC,SAAU,YAAa,cAAe,4CAC1C,SAASC,EAAGC,KAAMC,OAAQC,cAClBC,IAAMF,OAAOG,QAAU,sCASlBC,mBAAmBC,gBACjBA,QAAQC,QAAQ,gBAAgBC,KAAK,yBAG7C,CACHC,KAAM,SAASH,SACXA,QAAUP,EAAEO,aACRI,QAAUX,EAAEY,WAQZC,SAAW,CACXC,KAAM,OACNC,SAAU,OACVC,KAVO,CACPC,UAAWV,QAAQE,KAAK,gBACxBS,GAAIX,QAAQE,KAAK,iBACjBK,KAAMP,QAAQE,KAAK,kBACnBU,QAASjB,OAAOiB,QAChBC,SAAUd,mBAAmBC,kBAQjCP,EAAEC,KAAKG,IAAKS,UAAUQ,MAAK,SAASC,OAChCnB,SAASoB,OAAOhB,QAASe,OACzBX,QAAQa,aAGLb"}PK'9\x$navigation/amd/build/navblock.min.jsnu[define("block_navigation/navblock",["exports","core_block/events","core/tree"],(function(_exports,_events,_tree){var obj; /** * Load the navigation tree javascript. * * @module block_navigation/navblock * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_tree=(obj=_tree)&&obj.__esModule?obj:{default:obj};_exports.init=instanceId=>{const navTree=new _tree.default(".block_navigation .block_tree"),blockNode=document.querySelector('[data-instance-id="'.concat(instanceId,'"]'));navTree.finishExpandingGroup=item=>{_tree.default.prototype.finishExpandingGroup.call(navTree,item),(0,_events.notifyBlockContentUpdated)(blockNode)},navTree.collapseGroup=item=>{_tree.default.prototype.collapseGroup.call(navTree,item),(0,_events.notifyBlockContentUpdated)(blockNode)}}})); //# sourceMappingURL=navblock.min.js.mapPK'9\{i 1navigation/amd/build/site_admin_loader.min.js.mapnu[{"version":3,"file":"site_admin_loader.min.js","sources":["../src/site_admin_loader.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Load the site admin nav tree via ajax and render the response.\n *\n * @module block_navigation/site_admin_loader\n * @copyright 2015 John Okely \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/config', 'block_navigation/ajax_response_renderer'],\n function($, ajax, config, renderer) {\n\n var SITE_ADMIN_NODE_TYPE = 71;\n var URL = config.wwwroot + '/lib/ajax/getsiteadminbranch.php';\n\n return {\n load: function(element) {\n element = $(element);\n var promise = $.Deferred();\n var data = {\n type: SITE_ADMIN_NODE_TYPE,\n sesskey: config.sesskey\n };\n var settings = {\n type: 'POST',\n dataType: 'json',\n data: data\n };\n\n $.ajax(URL, settings).done(function(nodes) {\n renderer.render(element, nodes);\n promise.resolve();\n });\n\n return promise;\n }\n };\n});\n"],"names":["define","$","ajax","config","renderer","URL","wwwroot","load","element","promise","Deferred","settings","type","dataType","data","sesskey","done","nodes","render","resolve"],"mappings":";;;;;;;AAsBAA,4CAAO,CAAC,SAAU,YAAa,cAAe,4CACtC,SAASC,EAAGC,KAAMC,OAAQC,cAG1BC,IAAMF,OAAOG,QAAU,yCAEpB,CACHC,KAAM,SAASC,SACXA,QAAUP,EAAEO,aACRC,QAAUR,EAAES,WAKZC,SAAW,CACXC,KAAM,OACNC,SAAU,OACVC,KAPO,CACPF,KARe,GASfG,QAASZ,OAAOY,iBAQpBd,EAAEC,KAAKG,IAAKM,UAAUK,MAAK,SAASC,OAChCb,SAASc,OAAOV,QAASS,OACzBR,QAAQU,aAGLV"}PK'9\1-&navigation/amd/build/nav_loader.min.jsnu[/** * Load the nav tree items via ajax and render the response. * * @module block_navigation/nav_loader * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_navigation/nav_loader",["jquery","core/ajax","core/config","block_navigation/ajax_response_renderer"],(function($,ajax,config,renderer){var URL=config.wwwroot+"/lib/ajax/getnavbranch.php";function getBlockInstanceId(element){return element.closest("[data-block]").attr("data-instanceid")}return{load:function(element){element=$(element);var promise=$.Deferred(),settings={type:"POST",dataType:"json",data:{elementid:element.attr("data-node-id"),id:element.attr("data-node-key"),type:element.attr("data-node-type"),sesskey:config.sesskey,instance:getBlockInstanceId(element)}};return $.ajax(URL,settings).done((function(nodes){renderer.render(element,nodes),promise.resolve()})),promise}}})); //# sourceMappingURL=nav_loader.min.js.mapPK'9\h=9navigation/tests/behat/adminnistration_navigation.featurenu[@block @block_navigation Feature: Test that admin can see related nodes in Administration block In order to manage As an admin I need to be able to see related nodes in Administration block Background: Given the following "categories" exist: | name | category | idnumber | visible | | cat1 | 0 | cat1 | 1 | And the following "courses" exist: | fullname | shortname | category | visible | | Course 1 | c1 | cat1 | 1 | And the following config values are set as admin: | unaddableblocks | | theme_boost | And I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Administration" block if not present And I configure the "Administration" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" @javascript Scenario: As admin I must not see question related nodes in Administration. Given the following "activities" exist: | activity | name | intro | course | idnumber | | quiz | Quiz 1 | Quiz 1 for testing the Add menu | c1 | quiz1 | And I am on "Course 1" course homepage Then I should see "Question bank" And I should not see "Questions" And I am on the "Quiz 1" "mod_quiz > view" page And "Question bank" "link" should exist And "Questions" "link" should exist And "Categories" "link" should exist PK'9\8c'.navigation/tests/behat/view_my_courses.featurenu[@block @block_navigation Feature: View my courses in navigation block In order to navigate to my courses As a student I need my courses displayed in the navigation block Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And the following "categories" exist: | name | category | idnumber | | cat1 | 0 | cat1 | | cat2 | 0 | cat2 | | cat3 | 0 | cat3 | | cat31 | cat3 | cat31 | | cat32 | cat3 | cat32 | | cat33 | cat3 | cat33 | And the following "courses" exist: | fullname | shortname | category | | Course1 | c1 | cat1 | | Course2 | c2 | cat2 | | Course31 | c31 | cat31 | | Course32 | c32 | cat32 | | Course331| c331 | cat33 | | Course332| c332 | cat33 | And the following "course enrolments" exist: | user | course | role | | student1 | c1 | student | | student1 | c31 | student | | student1 | c331 | student | And I log in as "admin" And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Navigation" block if not present And I configure the "Navigation" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" And I log out @javascript Scenario: The plain list of enrolled courses is shown Given the following config values are set as admin: | navshowmycoursecategories | 0 | And I log in as "student1" When I click on "Dashboard" "link" in the "Navigation" "block" Then I should not see "cat1" in the "Navigation" "block" And I should not see "cat2" in the "Navigation" "block" And I should see "c1" in the "Navigation" "block" And I should see "c31" in the "Navigation" "block" And I should see "c331" in the "Navigation" "block" And I should not see "c2" in the "Navigation" "block" And I should not see "c32" in the "Navigation" "block" And I should not see "c332" in the "Navigation" "block" @javascript Scenario: The nested list of enrolled courses is shown Given the following config values are set as admin: | navshowmycoursecategories | 1 | And I log in as "student1" When I click on "Dashboard" "link" in the "Navigation" "block" Then I should see "cat1" in the "Navigation" "block" And I should see "cat3" in the "Navigation" "block" And I should not see "cat2" in the "Navigation" "block" And I expand "cat3" node And I should see "cat31" in the "Navigation" "block" And I should see "cat33" in the "Navigation" "block" And I should not see "cat32" in the "Navigation" "block" And I expand "cat31" node And I should see "c31" in the "Navigation" "block" And I expand "cat33" node And I should see "c331" in the "Navigation" "block" And I should not see "c332" in the "Navigation" "block" @javascript Scenario: I can expand categories and courses as guest Given the following config values are set as admin: | navshowmycoursecategories | 1 | | navshowallcourses | 1 | And I expand "Courses" node And I should see "cat1" in the "Navigation" "block" And I should see "cat2" in the "Navigation" "block" And I should see "cat3" in the "Navigation" "block" And I should not see "cat31" in the "Navigation" "block" And I should not see "cat32" in the "Navigation" "block" And I should not see "cat331" in the "Navigation" "block" And I should not see "c1" in the "Navigation" "block" And I should not see "c2" in the "Navigation" "block" And I should not see "c31" in the "Navigation" "block" And I should not see "c32" in the "Navigation" "block" When I expand "cat3" node And I expand "cat31" node And I expand "cat1" node Then I should see "cat1" in the "Navigation" "block" And I should see "cat2" in the "Navigation" "block" And I should see "cat3" in the "Navigation" "block" And I should see "cat31" in the "Navigation" "block" And I should see "cat32" in the "Navigation" "block" And I should not see "cat331" in the "Navigation" "block" And I should see "c1" in the "Navigation" "block" And I should not see "c2" in the "Navigation" "block" And I should see "c31" in the "Navigation" "block" And I should not see "c32" in the "Navigation" "block" @javascript Scenario: The course limit setting is applied in the navigation block Given the following config values are set as admin: | navcourselimit | 2 | And I log in as "student1" And I should see "More..." in the "Navigation" "block" When I click on "More..." "link" in the "Navigation" "block" Then I should see "My courses" in the "page-header" "region" @javascript Scenario: The dashboard node is not displayed in the navigation block when it is disabled Given the following config values are set as admin: | enabledashboard | 0 | When I log in as "student1" Then I should not see "Dashboard" in the "Navigation" "block" # Re-enable dashboard and check then it's displayed in the navigation block. And the following config values are set as admin: | enabledashboard | 1 | And I reload the page And I should see "Dashboard" in the "Navigation" "block" PK'9\x 0navigation/tests/behat/participants_link.featurenu[@block @block_navigation Feature: Displaying the link to the Participants page In order to see the course / site participants As a student / admin respectively I need a link to the Participants page be displayed (but only if I can access that page) Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | One | student1@example.com | | student2 | Student | Two | student2@example.com | And the following "courses" exist: | fullname | shortname | | Course1 | C1 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | And I log in as "admin" And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Navigation" block if not present And I configure the "Navigation" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" And I log out @javascript Scenario: Course participants link is displayed to enrolled students after expanding the course node When I log in as "student1" And I expand "C1" node Then "Participants" "link" should exist in the "Navigation" "block" And I click on "Participants" "link" in the "Navigation" "block" And I should see "Participants" And "Student One" "link" should exist And "Student Two" "link" should not exist @javascript Scenario: Course participants link is displayed to users depending on role permissions settings Given the following "activities" exist: | activity | course | name | | forum | C1 | Test forum name | And I am on the "Course1" "enrolment methods" page logged in as admin And I click on "Edit" "link" in the "Guest access" "table_row" And I set the following fields to these values: | Allow guest access | Yes | And I press "Save changes" When I am on the "Course1" course page logged in as guest Then I should not see "Participants" in the "Navigation" "block" And I am on the "Test forum name" "forum activity" page And I should not see "Participants" in the "Navigation" "block" And I log out And the following "role capability" exists: | role | guest | | moodle/course:viewparticipants | allow | And I am on the "Course1" course page logged in as guest And I should see "Participants" in the "Navigation" "block" And I am on the "Test forum name" "forum activity" page And I should see "Participants" in the "Navigation" "block" Scenario: Site participants link is displayed to admins When I log in as "admin" Then "Participants" "link" should exist in the "Navigation" "block" And I click on "Participants" "link" in the "Navigation" "block" And I should see "Participants" And "Student One" "link" should exist And "Student Two" "link" should exist @javascript Scenario: Site participants link is not displayed to students (MDL-55667) Given I log in as "admin" And I set the following administration settings values: | defaultfrontpageroleid | Student (student) | And I log out When I log in as "student2" And I expand "Site pages" node Then "Participants" "link" should not exist in the "Navigation" "block" PK'9\?$$2navigation/tests/behat/expand_courses_node.featurenu[@block @block_navigation Feature: Expand the courses nodes within the navigation block In order to navigate the site As an anonymous user, a guest, a student, and an admin I need to expand the courses node in the navigation block and check the display of courses and categories. Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "categories" exist: | name | category | idnumber | visible | | cat1 | 0 | cat1 | 1 | | cat2 | 0 | cat2 | 1 | | cat21 | cat2 | cat21 | 1 | | cat211 | cat21 | cat211 | 1 | | cat3 | 0 | cat3 | 0 | And the following "courses" exist: | fullname | shortname | category | visible | | Course 1 | c1 | cat1 | 1 | | Course 2 | c2 | cat2 | 1 | | Course 3 | c3 | cat21 | 1 | | Course 4 | c4 | cat211 | 1 | | Course 5 | c5 | cat211 | 0 | | Course 6 | c6 | cat211 | 0 | | Course 7 | c7 | cat3 | 1 | | Course 8 | c8 | cat3 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | c1 | teacher | | teacher1 | c3 | teacher | | teacher1 | c5 | teacher | | student1 | c1 | student | | student1 | c2 | student | | student1 | c4 | student | And the following config values are set as admin: | navshowallcourses | 1 | And I log in as "admin" And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Navigation" block if not present And I configure the "Navigation" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" And I turn editing mode off And I am on the "Course 2" "enrolment methods" page And I click on "Edit" "link" in the "Guest access" "table_row" And I set the following fields to these values: | Allow guest access | Yes | And I press "Save changes" And I log out @javascript Scenario: As an anonymous user I expand the courses node to see courses. When I should see "You are not logged in." in the ".logininfo" "css_element" And I should see "Home" in the "Navigation" "block" And I should see "Courses" in the "Navigation" "block" And I expand "Courses" node And I should see "cat1" in the "Navigation" "block" And I should see "cat2" in the "Navigation" "block" And I should not see "cat3" in the "Navigation" "block" And I expand "cat1" node And I expand "cat2" node And I should see "cat21" in the "Navigation" "block" And I expand "cat21" node And I should see "cat211" in the "Navigation" "block" And I expand "cat211" node Then I should see "c1" in the "Navigation" "block" And I should see "c2" in the "Navigation" "block" And I should see "c3" in the "Navigation" "block" And I should see "c4" in the "Navigation" "block" And I should not see "c5" in the "Navigation" "block" And I should not see "c6" in the "Navigation" "block" And navigation node "c1" should not be expandable And navigation node "c2" should not be expandable And navigation node "c3" should not be expandable And navigation node "c4" should not be expandable @javascript Scenario: As the admin user I expand the courses and category nodes to see courses. When I log in as "admin" And I am on site homepage And I should see "Site home" in the "Navigation" "block" And I should see "Courses" in the "Navigation" "block" And I expand "Courses" node And I should see "cat1" in the "Navigation" "block" And I should see "cat2" in the "Navigation" "block" And I should see "cat3" in the "Navigation" "block" And I expand "cat1" node And I expand "cat2" node And I expand "cat3" node And I should see "cat21" in the "Navigation" "block" And I expand "cat21" node And I should see "cat211" in the "Navigation" "block" And I expand "cat211" node Then I should see "c1" in the "Navigation" "block" And I should see "c2" in the "Navigation" "block" And I should see "c3" in the "Navigation" "block" And I should see "c4" in the "Navigation" "block" And I should see "c5" in the "Navigation" "block" And I should see "c6" in the "Navigation" "block" And I should see "c7" in the "Navigation" "block" And I should see "c8" in the "Navigation" "block" And navigation node "c1" should be expandable And navigation node "c2" should be expandable And navigation node "c3" should be expandable And navigation node "c4" should be expandable And navigation node "c5" should be expandable And navigation node "c6" should be expandable And navigation node "c7" should be expandable And navigation node "c8" should be expandable @javascript Scenario: As teacher1 I expand the courses and category nodes to see courses. When I log in as "teacher1" And I am on site homepage And I should see "Site home" in the "Navigation" "block" And I should see "Courses" in the "Navigation" "block" And I expand "Courses" node And I should see "cat1" in the "Navigation" "block" And I should see "cat2" in the "Navigation" "block" And I should not see "cat3" in the "Navigation" "block" And I expand "cat1" node And I expand "cat2" node And I should see "cat21" in the "Navigation" "block" And I expand "cat21" node And I should see "cat211" in the "Navigation" "block" And I expand "cat211" node Then I should see "c1" in the "Navigation" "block" And I should see "c2" in the "Navigation" "block" And I should see "c3" in the "Navigation" "block" And I should see "c4" in the "Navigation" "block" And I should see "c5" in the "Navigation" "block" And I should not see "c6" in the "Navigation" "block" And I should not see "c7" in the "Navigation" "block" And I should not see "c8" in the "Navigation" "block" And navigation node "c1" should be expandable And navigation node "c2" should be expandable And navigation node "c3" should be expandable And navigation node "c4" should not be expandable And navigation node "c5" should be expandable @javascript Scenario: As student1 I expand the courses and category nodes to see courses. When I log in as "student1" And I am on site homepage And I should see "Site home" in the "Navigation" "block" And I should see "Courses" in the "Navigation" "block" And I expand "Courses" node And I should see "cat1" in the "Navigation" "block" And I should see "cat2" in the "Navigation" "block" And I should not see "cat3" in the "Navigation" "block" And I expand "cat1" node And I expand "cat2" node And I should see "cat21" in the "Navigation" "block" And I expand "cat21" node And I should see "cat211" in the "Navigation" "block" And I expand "cat211" node Then I should see "c1" in the "Navigation" "block" And I should see "c2" in the "Navigation" "block" And I should see "c3" in the "Navigation" "block" And I should see "c4" in the "Navigation" "block" And I should not see "c5" in the "Navigation" "block" And I should not see "c6" in the "Navigation" "block" And I should not see "c7" in the "Navigation" "block" And I should not see "c8" in the "Navigation" "block" And navigation node "c1" should be expandable And navigation node "c2" should be expandable And navigation node "c3" should not be expandable And navigation node "c4" should be expandable @javascript Scenario: As guest I expand the courses and category nodes to see courses. When I log in as "guest" And I am on site homepage And I should see "Site home" in the "Navigation" "block" And I should see "Courses" in the "Navigation" "block" And I expand "Courses" node And I should see "cat1" in the "Navigation" "block" And I should see "cat2" in the "Navigation" "block" And I should not see "cat3" in the "Navigation" "block" And I expand "cat1" node And I expand "cat2" node And I should see "cat21" in the "Navigation" "block" And I expand "cat21" node And I should see "cat211" in the "Navigation" "block" And I expand "cat211" node Then I should see "c1" in the "Navigation" "block" And I should see "c2" in the "Navigation" "block" And I should see "c3" in the "Navigation" "block" And I should see "c4" in the "Navigation" "block" And I should not see "c5" in the "Navigation" "block" And I should not see "c6" in the "Navigation" "block" And I should not see "c7" in the "Navigation" "block" And I should not see "c8" in the "Navigation" "block" And navigation node "c1" should not be expandable And navigation node "c2" should be expandable And navigation node "c3" should not be expandable And navigation node "c4" should not be expandable PK'9\J Q1Q1navigation/block_navigation.phpnu[. /** * This file contains classes used to manage the navigation structures in Moodle * and was introduced as part of the changes occuring in Moodle 2.0 * * @since Moodle 2.0 * @package block_navigation * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * The global navigation tree block class * * Used to produce the global navigation block new to Moodle 2.0 * * @package block_navigation * @category navigation * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_navigation extends block_base { /** @var int This allows for multiple navigation trees */ public static $navcount; /** @var string The name of the block */ public $blockname = null; /** @var bool A switch to indicate whether content has been generated or not. */ protected $contentgenerated = false; /** @var bool|null variable for checking if the block is docked*/ protected $docked = null; /** @var int Trim characters from the right */ const TRIM_RIGHT = 1; /** @var int Trim characters from the left */ const TRIM_LEFT = 2; /** @var int Trim characters from the center */ const TRIM_CENTER = 3; /** * Set the initial properties for the block */ function init() { $this->blockname = get_class($this); $this->title = get_string('pluginname', $this->blockname); } /** * All multiple instances of this block * @return bool Returns false */ function instance_allow_multiple() { return false; } /** * Set the applicable formats for this block to all * @return array */ function applicable_formats() { return array('all' => true); } /** * Allow the user to configure a block instance * @return bool Returns true */ function instance_allow_config() { return true; } /** * The navigation block cannot be hidden by default as it is integral to * the navigation of Moodle. * * @return false */ function instance_can_be_hidden() { return false; } /** * Find out if an instance can be docked. * * @return bool true or false depending on whether the instance can be docked or not. */ function instance_can_be_docked() { return (parent::instance_can_be_docked() && (empty($this->config->enabledock) || $this->config->enabledock=='yes')); } /** * Gets Javascript that may be required for navigation */ function get_required_javascript() { parent::get_required_javascript(); $arguments = array( 'instanceid' => $this->instance->id ); $this->page->requires->string_for_js('viewallcourses', 'moodle'); $this->page->requires->js_call_amd('block_navigation/navblock', 'init', $arguments); } /** * Gets the content for this block by grabbing it from $this->page * * @return object $this->content */ function get_content() { global $CFG; // First check if we have already generated, don't waste cycles if ($this->contentgenerated === true) { return $this->content; } // JS for navigation moved to the standard theme, the code will probably have to depend on the actual page structure // $this->page->requires->js('/lib/javascript-navigation.js'); // Navcount is used to allow us to have multiple trees although I dont' know why // you would want two trees the same block_navigation::$navcount++; // Check if this block has been docked if ($this->docked === null) { $this->docked = get_user_preferences('nav_in_tab_panel_globalnav'.block_navigation::$navcount, 0); } // Check if there is a param to change the docked state if ($this->docked && optional_param('undock', null, PARAM_INT)==$this->instance->id) { unset_user_preference('nav_in_tab_panel_globalnav'.block_navigation::$navcount); $url = $this->page->url; $url->remove_params(array('undock')); redirect($url); } else if (!$this->docked && optional_param('dock', null, PARAM_INT)==$this->instance->id) { set_user_preferences(array('nav_in_tab_panel_globalnav'.block_navigation::$navcount=>1)); $url = $this->page->url; $url->remove_params(array('dock')); redirect($url); } $trimmode = self::TRIM_RIGHT; $trimlength = 50; if (!empty($this->config->trimmode)) { $trimmode = (int)$this->config->trimmode; } if (!empty($this->config->trimlength)) { $trimlength = (int)$this->config->trimlength; } // Get the navigation object or don't display the block if none provided. if (!$navigation = $this->get_navigation()) { return null; } $expansionlimit = null; if (!empty($this->config->expansionlimit)) { $expansionlimit = $this->config->expansionlimit; $navigation->set_expansion_limit($this->config->expansionlimit); } $this->trim($navigation, $trimmode, $trimlength, ceil($trimlength/2)); // Get the expandable items so we can pass them to JS $expandable = array(); $navigation->find_expandable($expandable); if ($expansionlimit) { foreach ($expandable as $key=>$node) { if ($node['type'] > $expansionlimit && !($expansionlimit == navigation_node::TYPE_COURSE && $node['type'] == $expansionlimit && $node['branchid'] == SITEID)) { unset($expandable[$key]); } } } $limit = 20; if (!empty($CFG->navcourselimit)) { $limit = $CFG->navcourselimit; } $expansionlimit = 0; if (!empty($this->config->expansionlimit)) { $expansionlimit = $this->config->expansionlimit; } $options = array(); $options['linkcategories'] = (!empty($this->config->linkcategories) && $this->config->linkcategories == 'yes'); // Grab the items to display $renderer = $this->page->get_renderer($this->blockname); $this->content = new stdClass(); $this->content->text = $renderer->navigation_tree($navigation, $expansionlimit, $options); // Set content generated to true so that we know it has been done $this->contentgenerated = true; return $this->content; } /** * Returns the navigation * * @return navigation_node The navigation object to display */ protected function get_navigation() { // Initialise (only actually happens if it hasn't already been done yet) $this->page->navigation->initialise(); return clone($this->page->navigation); } /** * Returns the attributes to set for this block * * This function returns an array of HTML attributes for this block including * the defaults. * {@link block_tree::html_attributes()} is used to get the default arguments * and then we check whether the user has enabled hover expansion and add the * appropriate hover class if it has. * * @return array An array of HTML attributes */ public function html_attributes() { $attributes = parent::html_attributes(); if (!empty($this->config->enablehoverexpansion) && $this->config->enablehoverexpansion == 'yes') { $attributes['class'] .= ' block_js_expansion'; } return $attributes; } /** * Trims the text and shorttext properties of this node and optionally * all of its children. * * @param navigation_node $node * @param int $mode One of navigation_node::TRIM_* * @param int $long The length to trim text to * @param int $short The length to trim shorttext to * @param bool $recurse Recurse all children */ public function trim(navigation_node $node, $mode=1, $long=50, $short=25, $recurse=true) { switch ($mode) { case self::TRIM_RIGHT : if (core_text::strlen($node->text)>($long+3)) { // Truncate the text to $long characters $node->text = $this->trim_right($node->text, $long); } if (is_string($node->shorttext) && core_text::strlen($node->shorttext)>($short+3)) { // Truncate the shorttext $node->shorttext = $this->trim_right($node->shorttext, $short); } break; case self::TRIM_LEFT : if (core_text::strlen($node->text)>($long+3)) { // Truncate the text to $long characters $node->text = $this->trim_left($node->text, $long); } if (is_string($node->shorttext) && core_text::strlen($node->shorttext)>($short+3)) { // Truncate the shorttext $node->shorttext = $this->trim_left($node->shorttext, $short); } break; case self::TRIM_CENTER : if (core_text::strlen($node->text)>($long+3)) { // Truncate the text to $long characters $node->text = $this->trim_center($node->text, $long); } if (is_string($node->shorttext) && core_text::strlen($node->shorttext)>($short+3)) { // Truncate the shorttext $node->shorttext = $this->trim_center($node->shorttext, $short); } break; } if ($recurse && $node->children->count()) { foreach ($node->children as &$child) { $this->trim($child, $mode, $long, $short, true); } } } /** * Truncate a string from the left * @param string $string The string to truncate * @param int $length The length to truncate to * @return string The truncated string */ protected function trim_left($string, $length) { return '...'.core_text::substr($string, core_text::strlen($string)-$length, $length); } /** * Truncate a string from the right * @param string $string The string to truncate * @param int $length The length to truncate to * @return string The truncated string */ protected function trim_right($string, $length) { return core_text::substr($string, 0, $length).'...'; } /** * Truncate a string in the center * @param string $string The string to truncate * @param int $length The length to truncate to * @return string The truncated string */ protected function trim_center($string, $length) { $trimlength = ceil($length/2); $start = core_text::substr($string, 0, $trimlength); $end = core_text::substr($string, core_text::strlen($string)-$trimlength); $string = $start.'...'.$end; return $string; } /** * Returns the role that best describes the navigation block... 'navigation' * * @return string 'navigation' */ public function get_aria_role() { return 'navigation'; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = !empty($this->config) ? $this->config : new stdClass(); return (object) [ 'instance' => $configs, 'plugin' => new stdClass(), ]; } } PK'9\ތWM""activity_modules/db/access.phpnu[. /** * Activity modules block caps. * * @package block_activity_modules * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/activity_modules:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\}3activity_modules/lang/en/block_activity_modules.phpnu[. /** * Strings for component 'block_activity_modules', language 'en', branch 'MOODLE_20_STABLE' * * @package block_activity_modules * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['activity_modules:addinstance'] = 'Add a new activities block'; $string['pluginname'] = 'Activities'; $string['privacy:metadata'] = 'The Activities block only shows data stored in other locations.'; PK'9\KNactivity_modules/version.phpnu[. /** * Version details * * @package block_activity_modules * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_activity_modules'; // Full name of the plugin (used for diagnostics) PK'9\$V+activity_modules/block_activity_modules.phpnu[. /** * This file contains the Activity modules block. * * @package block_activity_modules * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/filelib.php'); class block_activity_modules extends block_list { function init() { $this->title = get_string('pluginname', 'block_activity_modules'); } function get_content() { global $CFG, $DB, $OUTPUT; if($this->content !== NULL) { return $this->content; } $this->content = new stdClass; $this->content->items = array(); $this->content->icons = array(); $this->content->footer = ''; $course = $this->page->course; require_once($CFG->dirroot.'/course/lib.php'); $modinfo = get_fast_modinfo($course); $modfullnames = array(); $archetypes = array(); foreach($modinfo->cms as $cm) { // Exclude activities that aren't visible or have no view link (e.g. label). Account for folder being displayed inline. if ( !\course_modinfo::is_mod_type_visible_on_course($cm->modname) || !$cm->uservisible || (!$cm->has_view() && strcmp($cm->modname, 'folder') !== 0) ) { continue; } if (array_key_exists($cm->modname, $modfullnames)) { continue; } if (!array_key_exists($cm->modname, $archetypes)) { $archetypes[$cm->modname] = plugin_supports('mod', $cm->modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); } if ($archetypes[$cm->modname] == MOD_ARCHETYPE_RESOURCE) { if (!array_key_exists('resources', $modfullnames)) { $modfullnames['resources'] = get_string('resources'); } } else { $modfullnames[$cm->modname] = $cm->modplural; } } core_collator::asort($modfullnames); foreach ($modfullnames as $modname => $modfullname) { if ($modname === 'resources') { $icon = $OUTPUT->pix_icon('monologo', '', 'mod_page', array('class' => 'icon')); $this->content->items[] = ''.$icon.$modfullname.''; } else { $icon = $OUTPUT->image_icon('monologo', get_string('pluginname', $modname), $modname); $this->content->items[] = ''.$icon.$modfullname.''; } } return $this->content; } /** * Returns the role that best describes this blocks contents. * * This returns 'navigation' as the blocks contents is a list of links to activities and resources. * * @return string 'navigation' */ public function get_aria_role() { return 'navigation'; } function applicable_formats() { return array('all' => true, 'mod' => false, 'my' => false, 'admin' => false, 'tag' => false); } } PK'9\'Uf-activity_modules/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_activity_modules. * * @package block_activity_modules * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_activity_modules\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_activity_modules implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\%'';activity_modules/tests/behat/block_activity_modules.featurenu[@block @block_activity_modules Feature: Block activity modules In order to overview activity modules in a course As a manager I can add activities block in a course or on the frontpage Scenario: Add activities block on the frontpage Given the following "activities" exist: | activity | name | intro | course | idnumber | | assign | Frontpage assignment name | Frontpage assignment description | Acceptance test site | assign0 | | book | Frontpage book name | Frontpage book description | Acceptance test site | book0 | | choice | Frontpage choice name | Frontpage choice description | Acceptance test site | choice0 | | data | Frontpage database name | Frontpage database description | Acceptance test site | data0 | | feedback | Frontpage feedback name | Frontpage feedback description | Acceptance test site | feedback0 | | forum | Frontpage forum name | Frontpage forum description | Acceptance test site | forum0 | | label | Frontpage label name | Frontpage label description | Acceptance test site | label0 | | lti | Frontpage lti name | Frontpage lti description | Acceptance test site | lti0 | | page | Frontpage page name | Frontpage page description | Acceptance test site | page0 | | qbank | Frontpage qbank name | Frontpage qbank description | Acceptance test site | qbank0 | | quiz | Frontpage quiz name | Frontpage quiz description | Acceptance test site | quiz0 | | resource | Frontpage resource name | Frontpage resource description | Acceptance test site | resource0 | | imscp | Frontpage imscp name | Frontpage imscp description | Acceptance test site | imscp0 | | folder | Frontpage folder name | Frontpage folder description | Acceptance test site | folder0 | | glossary | Frontpage glossary name | Frontpage glossary description | Acceptance test site | glossary0 | | scorm | Frontpage scorm name | Frontpage scorm description | Acceptance test site | scorm0 | | lesson | Frontpage lesson name | Frontpage lesson description | Acceptance test site | lesson0 | | url | Frontpage url name | Frontpage url description | Acceptance test site | url0 | | wiki | Frontpage wiki name | Frontpage wiki description | Acceptance test site | wiki0 | | workshop | Frontpage workshop name | Frontpage workshop description | Acceptance test site | workshop0 | | qbank | Frontpage qbank name | Frontpage qbank description | Acceptance test site | qbank0 | When I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Activities" block And I click on "Assignments" "link" in the "Activities" "block" Then I should see "Frontpage assignment name" And I am on site homepage And I click on "Choices" "link" in the "Activities" "block" And I should see "Frontpage choice name" And I am on site homepage And I click on "Databases" "link" in the "Activities" "block" And I should see "Frontpage database name" And I am on site homepage And I click on "Feedback" "link" in the "Activities" "block" And I should see "Frontpage feedback name" And I am on site homepage And I click on "Forums" "link" in the "Activities" "block" And I should see "Frontpage forum name" And I am on site homepage And I click on "External tools" "link" in the "Activities" "block" And I should see "Frontpage lti name" And I am on site homepage And I click on "Quizzes" "link" in the "Activities" "block" And I should see "Frontpage quiz name" And I am on site homepage And I click on "Glossaries" "link" in the "Activities" "block" And I should see "Frontpage glossary name" And I am on site homepage And I click on "SCORM packages" "link" in the "Activities" "block" And I should see "Frontpage scorm name" And I am on site homepage And I click on "Lessons" "link" in the "Activities" "block" And I should see "Frontpage lesson name" And I am on site homepage And I click on "Wikis" "link" in the "Activities" "block" And I should see "Frontpage wiki name" And I am on site homepage And I click on "Workshop" "link" in the "Activities" "block" And I should see "Frontpage workshop name" And I am on site homepage And I click on "Resources" "link" in the "Activities" "block" And I should see "Frontpage book name" And I should see "Frontpage page name" And I should see "Frontpage resource name" And I should see "Frontpage imscp name" And I should see "Frontpage folder name" And I should see "Frontpage url name" And I should not see "Frontpage qbank name" And I am on site homepage And "Question banks" "link" should not exist in the "Activities" "block" Scenario: Add activities block in a course Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "activities" exist: | activity | name | intro | course | idnumber | | assign | Test assignment name | Test assignment description | C1 | assign1 | | book | Test book name | Test book description | C1 | book1 | | choice | Test choice name | Test choice description | C1 | choice1 | | data | Test database name | Test database description | C1 | data1 | | feedback | Test feedback name | Test feedback description | C1 | feedback1 | | folder | Test folder name | Test folder description | C1 | folder1 | | forum | Test forum name | Test forum description | C1 | forum1 | | glossary | Test glossary name | Test glossary description | C1 | glossary1 | | imscp | Test imscp name | Test imscp description | C1 | imscp1 | | label | Test label name | Test label description | C1 | label1 | | lesson | Test lesson name | Test lesson description | C1 | lesson1 | | lti | Test lti name | Test lti description | C1 | lti1 | | page | Test page name | Test page description | C1 | page1 | | qbank | Test qbank name | Test qbank description | C1 | qbank1 | | quiz | Test quiz name | Test quiz description | C1 | quiz1 | | resource | Test resource name | Test resource description | C1 | resource1 | | scorm | Test scorm name | Test scorm description | C1 | scorm1 | | url | Test url name | Test url description | C1 | url1 | | wiki | Test wiki name | Test wiki description | C1 | wiki1 | | workshop | Test workshop name | Test workshop description | C1 | workshop1 | | qbank | Test qbank name | Test qbank description | C1 | qbank1 | When I log in as "admin" And I am on "Course 1" course homepage with editing mode on And I add the "Activities" block And I click on "Assignments" "link" in the "Activities" "block" Then I should see "Test assignment name" And I am on "Course 1" course homepage And I click on "Choices" "link" in the "Activities" "block" And I should see "Test choice name" And I am on "Course 1" course homepage And I click on "Databases" "link" in the "Activities" "block" And I should see "Test database name" And I am on "Course 1" course homepage And I click on "Feedback" "link" in the "Activities" "block" And I should see "Test feedback name" And I am on "Course 1" course homepage And I click on "Forums" "link" in the "Activities" "block" And I should see "Test forum name" And I am on "Course 1" course homepage And I click on "External tools" "link" in the "Activities" "block" And I should see "Test lti name" And I am on "Course 1" course homepage And I click on "Quizzes" "link" in the "Activities" "block" And I should see "Test quiz name" And I am on "Course 1" course homepage And I click on "Glossaries" "link" in the "Activities" "block" And I should see "Test glossary name" And I am on "Course 1" course homepage And I click on "SCORM packages" "link" in the "Activities" "block" And I should see "Test scorm name" And I am on "Course 1" course homepage And I click on "Lessons" "link" in the "Activities" "block" And I should see "Test lesson name" And I am on "Course 1" course homepage And I click on "Wikis" "link" in the "Activities" "block" And I should see "Test wiki name" And I am on "Course 1" course homepage And I click on "Workshop" "link" in the "Activities" "block" And I should see "Test workshop name" And I am on "Course 1" course homepage And I click on "Resources" "link" in the "Activities" "block" And I should see "Test book name" And I should see "Test page name" And I should see "Test resource name" And I should see "Test imscp name" And I should see "Test folder name" And I should see "Test url name" And I should not see "Test qbank name" And I am on "Course 1" course homepage And "Question banks" "link" should not exist in the "Activities" "block" PK'9\KwRRsite_main_menu/styles.cssnu[/* Imitate mobile grid for activity card. */ .block_site_main_menu .activity-item .activity-grid { grid-template-columns: min-content 1fr min-content min-content min-content; grid-template-rows: 1fr repeat(4, min-content); grid-template-areas: "icon name actions" "visibility visibility visibility" "dates dates dates" "completion completion completion" "altcontent altcontent altcontent" "afterlink afterlink afterlink" "availability availability availability"; } .block_site_main_menu .activity-item .activity-grid.noname-grid { grid-template-columns: 1fr min-content; grid-template-areas: "actions" "visibility" "altcontent" "groupmode" "afterlink" "completion" "availability"; } .block_site_main_menu .activity-item .activity-grid.noname-grid .activity-actions { justify-self: end; } /* Hide extra edit elements in block space. */ .block_site_main_menu .activity-groupmode-info { display: none; } PK'9\w٢site_main_menu/db/access.phpnu[. /** * Site main menu block caps. * * @package block_site_main_menu * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/site_main_menu:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\ª/site_main_menu/lang/en/block_site_main_menu.phpnu[. /** * Strings for component 'block_site_main_menu', language 'en', branch 'MOODLE_20_STABLE' * * @package block_site_main_menu * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['pluginname'] = 'Main menu'; $string['site_main_menu:addinstance'] = 'Add a new main menu block'; $string['privacy:metadata'] = 'The Main menu block only shows data stored in other locations.'; PK'9\9site_main_menu/version.phpnu[. /** * Version details * * @package block_site_main_menu * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_site_main_menu'; // Full name of the plugin (used for diagnostics) PK'9\ՎT+site_main_menu/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_site_main_menu. * * @package block_site_main_menu * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_site_main_menu\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_site_main_menu implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\ О  -site_main_menu/classes/output/mainsection.phpnu[. namespace block_site_main_menu\output; use core_courseformat\base as courseformat; use renderable; use section_info; use templatable; /** * Class mainsection * * @package block_site_main_menu * @copyright 2024 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class mainsection implements renderable, templatable { /** * The class constructor. * * @param courseformat $format the course format instance * @param section_info $section the section to render */ public function __construct( /** @var courseformat $format the course format instance. */ protected courseformat $format, /** @var section_info $section the section to render. */ protected section_info $section, ) { } /** * Export for template. * * @param \renderer_base $output * @return array */ public function export_for_template(\renderer_base $output) { $format = $this->format; $course = $format->get_course(); $section = $this->section; $sectionoutputclass = $format->get_output_classname('content\\section\\cmlist'); $sectionoutput = new $sectionoutputclass($format, $section); $cmlist = $output->render($sectionoutput); return [ 'siteid' => $course->id, 'cmlist' => $cmlist, 'sectionid' => $section->id, 'sectionname' => $format->get_section_name($section), 'sectionnum' => $section->sectionnum, 'editing' => $format->show_editor(), ]; } } PK'9\P -site_main_menu/templates/mainsection.mustachenu[{{! 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 . }} {{! @template block_site_main_menu/mainsection This mustache emulates a course single section structure inside a block. It requires to include several divs to emulate the structure of a course format. Example context (json): { "cmlist": "Sample", "siteid": 1, "sectionid": 1, "sectionname": "Sample", "sectionnum": 1, "editing": true } }}
          {{! The section list is used by the content module to init the sections.}}
          {{! The section need some bottom padding and margin for the dropzone. Otherwise the activities will cover the area. }}
          {{#editing}} {{! The section header is used as a dropzone when the section is empty.}}
           
          {{/editing}} {{{cmlist}}}
          {{#js}} {{! The block should be fast to load, we only load the editor when needed.}} {{#editing}} require( [ 'core_courseformat/local/content', 'core_courseformat/courseeditor' ], function( Component, Courseeditor ) { {{! The block could be included static in other courses so we use Courseeditor.getCourseEditor. }} new Component({ element: document.getElementById('block_site_main_menu_section'), reactive: Courseeditor.getCourseEditor({{siteid}}), }); } ); {{/editing}} {{/js}} PK'9\нtsite_main_menu/UPGRADING.mdnu[# block_site_main_menu Upgrade notes ## 5.0 ### Removed - 'Activity' selector in site_main_menu block has been deleted. For more information see [MDL-83733](https://tracker.moodle.org/browse/MDL-83733) PK'9\5!!2site_main_menu/tests/behat/edit_activities.featurenu[@block @block_site_main_menu Feature: Edit activities in main menu block In order to use main menu block As an admin I need to add and edit activities there @javascript Scenario: Edit name of acitivity in-place in site main menu block Given the following "activity" exists: | activity | forum | | course | Acceptance test site | | name | My forum name | | idnumber | forum | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | site_main_menu | System | 1 | site-index | side-pre | And I log in as "admin" And I am on site homepage And I turn editing mode on When I set the field "Edit title" in the "My forum name" "activity" to "New forum name" Then I should not see "My forum name" And I should see "New forum name" And I follow "New forum name" And I should not see "My forum name" And I should see "New forum name" @javascript Scenario: Activities in main menu block can be made available but not visible on a course page Given the following config values are set as admin: | allowstealth | 1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | site_main_menu | System | 1 | site-index | side-pre | And the following "activities" exist: | activity | course | section | name | | forum | Acceptance test site | 0 | Visible forum | | forum | Acceptance test site | 0 | My forum name | And I log in as "admin" And I am on site homepage And I turn editing mode on When I open "My forum name" actions menu And I choose "Availability > Make available but don't show on course page" in the open action menu Then I should see "Available but not shown on course page" in the "My forum name" "core_courseformat > Activity visibility" # Make sure that "Availability" dropdown in the edit menu has three options. And I open "My forum name" actions menu And I choose "Edit settings" in the open action menu And I expand all fieldsets And the "Availability" select box should contain "Show on course page" And the "Availability" select box should contain "Hide on course page" And the field "Availability" matches value "Make available but don't show on course page" And I press "Save and return to course" And I should see "Available but not shown on course page" in the "My forum name" "core_courseformat > Activity visibility" And I turn editing mode off And I should see "Available but not shown on course page" in the "My forum name" "core_courseformat > Activity visibility" And I log out And I should not see "My forum name" in the "Main menu" "block" And I should see "Visible forum" in the "Main menu" "block" @javascript Scenario: The move activity modal allow to move from the main menu block to the main content Given the following "activity" exists: | activity | forum | | course | Acceptance test site | | name | My forum name | | idnumber | forum | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | site_main_menu | System | 1 | site-index | side-pre | And I log in as "admin" And I am on site homepage And I turn editing mode on And I should see "My forum name" in the "block_site_main_menu_section" "region" And I should not see "My forum name" in the "region-main" "region" When I open "My forum name" actions menu And I click on "Move" "link" in the "My forum name" activity And I should see "My forum name" in the "Move activity" "dialogue" And I should see "Block" in the "Move activity" "dialogue" And I should see "Site" in the "Move activity" "dialogue" And I click on "Site" "link" in the "Move activity" "dialogue" Then I should see "My forum name" in the "region-main" "region" And I should not see "My forum name" in the "block_site_main_menu_section" "region" @javascript Scenario: The move activity modal allow to move from the main content to the main menu block Given the following "activity" exists: | activity | forum | | course | Acceptance test site | | name | My forum name | | idnumber | forum | | section | 1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | site_main_menu | System | 1 | site-index | side-pre | And I log in as "admin" And I am on site homepage And I turn editing mode on And I should not see "My forum name" in the "block_site_main_menu_section" "region" And I should see "My forum name" in the "region-main" "region" When I open "My forum name" actions menu And I click on "Move" "link" in the "My forum name" activity And I should see "My forum name" in the "Move activity" "dialogue" And I should see "Block" in the "Move activity" "dialogue" And I should see "Site" in the "Move activity" "dialogue" And I click on "Block" "link" in the "Move activity" "dialogue" Then I should not see "My forum name" in the "region-main" "region" And I should see "My forum name" in the "block_site_main_menu_section" "region" @javascript Scenario: Admin can delete an activity in the main menu block Given the following "activity" exists: | activity | forum | | course | Acceptance test site | | name | My forum name | | idnumber | forum | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | site_main_menu | System | 1 | site-index | side-pre | And I log in as "admin" And I am on site homepage And I turn editing mode on And I should see "My forum name" in the "block_site_main_menu_section" "region" When I open "My forum name" actions menu And I choose "Delete" in the open action menu And I click on "Delete" "button" in the "Delete activity?" "dialogue" Then I should not see "My forum name" in the "block_site_main_menu_section" "region" @javascript Scenario: Admin can duplicate an activity in the main menu block Given the following "activity" exists: | activity | forum | | course | Acceptance test site | | name | My forum name | | idnumber | forum | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | site_main_menu | System | 1 | site-index | side-pre | And I log in as "admin" And I am on site homepage And I turn editing mode on And I should see "My forum name" in the "block_site_main_menu_section" "region" When I open "My forum name" actions menu And I choose "Duplicate" in the open action menu Then I should see "My forum name (copy)" in the "block_site_main_menu_section" "region" @javascript Scenario: Admin can move right and left an activity in the main menu block Given the following "activity" exists: | activity | forum | | course | Acceptance test site | | name | My forum name | | idnumber | forum | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | site_main_menu | System | 1 | site-index | side-pre | And I log in as "admin" And I am on site homepage And I turn editing mode on And I should see "My forum name" in the "block_site_main_menu_section" "region" When I open "My forum name" actions menu And "Move right" "link" should be visible And "Move left" "link" should not be visible And I choose "Move right" in the open action menu Then I open "My forum name" actions menu And "Move right" "link" should not be visible And "Move left" "link" should be visible And I choose "Move left" in the open action menu And I open "My forum name" actions menu And "Move right" "link" should be visible And "Move left" "link" should not be visible PK'9\##Dsite_main_menu/tests/behat/behat_block_site_main_menu_deprecated.phpnu[. // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../../../lib/behat/behat_deprecated_base.php'); use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\ExpectationException as ExpectationException; use Behat\Mink\Exception\DriverException as DriverException; use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; /** * Behat steps in plugin block_site_main_menu * * @package block_site_main_menu * @category test * @copyright 2024 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_block_site_main_menu_deprecated extends behat_deprecated_base { /** * Returns the DOM node of the activity in the site menu block * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @throws ElementNotFoundException Thrown by behat_base::find * @param string $activityname The activity name * @return NodeElement */ protected function get_site_menu_activity_node($activityname) { $activityname = behat_context_helper::escape($activityname); $xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]//li[contains(., $activityname)]"; return $this->find('xpath', $xpath); } /** * Clicks on the specified element of the activity. You should be in the course page with editing mode turned on. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Given /^I click on "(?P(?:[^"]|\\")*)" "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" activity in site main menu block$/ * @param string $element * @param string $selectortype * @param string $activityname */ public function i_click_on_in_the_activity_in_site_main_menu_block($element, $selectortype, $activityname) { $this->deprecated_message([ 'behat_block_site_main_menu::i_click_on_in_the_activity_in_site_main_menu_block is deprecated', 'Use: I open ACTIVITYNAME actions menu & I choose OPTIONTEXT in the open action menu', ]); $element = $this->get_site_menu_activity_element($element, $selectortype, $activityname); $element->click(); } /** * Clicks on the specified element inside the activity container. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @throws ElementNotFoundException * @param string $element * @param string $selectortype * @param string $activityname * @return NodeElement */ protected function get_site_menu_activity_element($element, $selectortype, $activityname) { $activitynode = $this->get_site_menu_activity_node($activityname); $exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '{$activityname}'"); return $this->find($selectortype, $element, $exception, $activitynode); } /** * Checks that the specified activity's action menu contains an item. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Then /^"(?P(?:[^"]|\\")*)" activity in site main menu block should have "(?P(?:[^"]|\\")*)" editing icon$/ * @param string $activityname * @param string $iconname */ public function activity_in_site_main_menu_block_should_have_editing_icon($activityname, $iconname) { $this->deprecated_message([ 'behat_block_site_main_menu::activity_in_site_main_menu_block_should_have_editing_icon is deprecated', 'Use: I should see WHATEVER in the ACTIVITYNAME "activity"', ]); $activitynode = $this->get_site_menu_activity_node($activityname); $notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' . $iconname . '" editing icon', $this->getSession()); $this->find('named_partial', ['link', $iconname], $notfoundexception, $activitynode); } /** * Checks that the specified activity's action menu contains an item. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Then /^"(?P(?:[^"]|\\")*)" activity in site main menu block should not have "(?P(?:[^"]|\\")*)" editing icon$/ * @param string $activityname * @param string $iconname */ public function activity_in_site_main_menu_block_should_not_have_editing_icon($activityname, $iconname) { $this->deprecated_message([ 'behat_block_site_main_menu::activity_in_site_main_menu_block_should_not_have_editing_icon is deprecated', 'Use: I should not see WHATEVER in the ACTIVITYNAME "activity"', ]); $activitynode = $this->get_site_menu_activity_node($activityname); try { $this->find('named_partial', array('link', $iconname), false, $activitynode); throw new ExpectationException('"' . $activityname . '" has a "' . $iconname . '" editing icon when it should not', $this->getSession()); } catch (ElementNotFoundException $e) { // This is good, the menu item should not be there. } } /** * Checks that the specified activity is hidden. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Then /^"(?P(?:[^"]|\\")*)" activity in site main menu block should be hidden$/ * @param string $activityname */ public function activity_in_site_main_menu_block_should_be_hidden($activityname) { $this->deprecated_message([ 'behat_block_site_main_menu::activity_in_site_main_menu_block_should_be_hidden is deprecated', 'Use: I should see "Hidden from students" in the "ACTIVITYNAME" "core_courseformat > Activity visibility"', ]); $activitynode = $this->get_site_menu_activity_node($activityname); $exception = new ExpectationException('"' . $activityname . '" is not hidden', $this->getSession()); $this->find('named_partial', ['badge', get_string('hiddenfromstudents')], $exception, $activitynode); } /** * Checks that the specified activity is hidden. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Then /^"(?P(?:[^"]|\\")*)" activity in site main menu block should be available but hidden from course page$/ * @param string $activityname */ public function activity_in_site_main_menu_block_should_be_available_but_hidden_from_course_page($activityname) { $this->deprecated_message([ 'behat_block_site_main_menu::activity_in_site_main_menu_block_should_be_available_but_hidden_from_course_page is deprecated', 'Use: I should see "Available but not shown on course page" in the "ACTIVITYNAME" "core_courseformat > Activity visibility"', ]); $activitynode = $this->get_site_menu_activity_node($activityname); $exception = new ExpectationException('"' . $activityname . '" is not hidden but available', $this->getSession()); $this->find('named_partial', ['badge', get_string('hiddenoncoursepage')], $exception, $activitynode); } /** * Opens an activity actions menu if it is not already opened. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Given /^I open "(?P(?:[^"]|\\")*)" actions menu in site main menu block$/ * @throws DriverException The step is not available when Javascript is disabled * @param string $activityname */ public function i_open_actions_menu_in_site_main_menu_block($activityname) { $this->deprecated_message([ 'behat_block_site_main_menu::i_open_actions_menu_in_site_main_menu_block is deprecated', 'Use: I open "ACTIVITYNAME" actions menu', ]); $activityname = behat_context_helper::escape($activityname); $xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]//li[contains(., $activityname)]"; $this->execute('behat_action_menu::i_open_the_action_menu_in', [$xpath, 'xpath_element']); } } PK'9\<*site_main_menu/tests/behat/add_url.featurenu[@block @block_site_main_menu Feature: Add URL to main menu block In order to add helpful resources for students As a admin I need to add URLs to the main menu block and check it works. Background: Given the following "courses" exist: | fullname | shortname | category | enablecompletion | | Course 1 | C1 | 0 | 1 | | Course 2 | C2 | 0 | | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | site_main_menu | System | 1 | site-index | side-pre | @javascript Scenario: Add a URL in menu block and ensure it appears Given I log in as "admin" And I am on site homepage And the following "activity" exists: | activity | url | | course | Acceptance test site | | name | reference link | | intro | mooooooooodle | | externalurl | http://www.moodle.com | | section | 0 | When I am on the "reference link" "url activity editing" page And I expand all fieldsets And I set the following fields to these values: | id_display | In pop-up | And I press "Save and return to course" And I turn editing mode on Then "reference link" "link" should exist in the "Main menu" "block" And "Add an activity or resource" "button" should exist in the "Main menu" "block" @javascript Scenario: Add a URL in menu block can appear in the entire site When I log in as "admin" And I am on site homepage And I turn editing mode on And I configure the "Main menu" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" And the following "activity" exists: | activity | url | | course | Acceptance test site | | name | reference link | | intro | mooooooooodle | | externalurl | http://www.moodle.com | | section | 0 | And I am on the "reference link" "url activity editing" page And I expand all fieldsets And I set the following fields to these values: | id_display | Embed | And I press "Save and return to course" Then I click on "reference link" "link" in the "Main menu" "block" And "reference link" "link" should exist in the "Main menu" "block" And I am on the "C1" "Course" page And "reference link" "link" should exist in the "Main menu" "block" And I navigate to "Badges > Add a new badge" in site administration And "reference link" "link" should exist in the "Main menu" "block" @javascript Scenario: Add a URL in menu block can appear in any front page When I log in as "admin" And I am on site homepage And I turn editing mode on And I configure the "Main menu" block And I set the following fields to these values: | Page contexts | Display on the site home and any pages added to the site home. | And I press "Save changes" And the following "activity" exists: | activity | url | | course | Acceptance test site | | name | reference link | | intro | mooooooooodle | | externalurl | http://www.moodle.com | | section | 0 | And I am on the "reference link" "url activity editing" page And I expand all fieldsets And I set the following fields to these values: | id_display | Embed | And I press "Save and return to course" Then I click on "reference link" "link" in the "Main menu" "block" And "reference link" "link" should exist in the "Main menu" "block" And I am on the "C1" "Course" page And "Main menu" "block" should not exist And I navigate to "Badges > Add a new badge" in site administration And "Main menu" "block" should not exist @javascript Scenario: When the "Main Menu" block is displayed throrought the entire site, adding an URL in a course results in adding it in the course and not in the frontpage Given I log in as "admin" And I am on site homepage And I turn editing mode on And I configure the "Main menu" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" When the following "activity" exists: | activity | url | | course | C2 | | name | reference link | | intro | mooooooooodle | | externalurl | http://www.moodle.com | | section | 0 | | showdescription | 1 | And I am on the "reference link" "url activity editing" page And I expand all fieldsets And I set the following fields to these values: | id_display | In pop-up | And I press "Save and return to course" Then "reference link" "link" should not exist in the "Main menu" "block" And I should see "mooooooooodle" in the "region-main" "region" PK'9\ީ9  'site_main_menu/block_site_main_menu.phpnu[. defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/course/lib.php'); /** * Site main menu block. * * @package block_site_main_menu * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_site_main_menu extends block_base { function init() { $this->title = get_string('pluginname', 'block_site_main_menu'); } function applicable_formats() { return array('site' => true); } function get_content() { if ($this->content !== NULL) { return $this->content; } $this->content = new stdClass(); $this->content->text = ''; $this->content->footer = ''; if (empty($this->instance)) { return $this->content; } $course = get_site(); course_create_sections_if_missing($course, 0); $format = course_get_format($course); $modinfo = $format->get_modinfo(); $section = $modinfo->get_section_info(0); $courserenderer = $format->get_renderer($this->page); $output = new block_site_main_menu\output\mainsection($format, $section); $this->content->text = $courserenderer->render($output); if ($this->page->course->id === SITEID) { $this->content->footer = $courserenderer->course_section_add_cm_control( course: $course, section: 0, sectionreturn: null, displayoptions: ['inblock' => true], ); } return $this->content; } } PK'9\` social_activities/styles.cssnu[/* Imitate mobile grid for activity card. */ .block_social_activities .activity-item .activity-grid { grid-template-columns: min-content 1fr min-content min-content min-content; grid-template-rows: 1fr repeat(4, min-content); grid-template-areas: "icon name actions" "visibility visibility visibility" "dates dates dates" "completion completion completion" "altcontent altcontent altcontent" "afterlink afterlink afterlink" "availability availability availability"; } .block_social_activities .activity-item .activity-grid.noname-grid { grid-template-columns: 1fr min-content; grid-template-areas: "actions" "visibility" "altcontent" "groupmode" "afterlink" "completion" "availability"; } .block_social_activities .activity-item .activity-grid.noname-grid .activity-actions { justify-self: end; } /* Hide extra edit elements in block space. */ .block_social_activities .activity-groupmode-info { display: none; } .block_social_activities .inplaceeditable.inplaceeditingon input { width: 100%; } PK'9\ۮ%%social_activities/db/access.phpnu[. /** * Social activities block caps. * * @package block_social_activities * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/social_activities:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\3A5social_activities/lang/en/block_social_activities.phpnu[. /** * Strings for component 'block_social_activities', language 'en', branch 'MOODLE_20_STABLE' * * @package block_social_activities * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['pluginname'] = 'Social activities'; $string['social_activities:addinstance'] = 'Add a new social activities block'; $string['privacy:metadata'] = 'The Social activities block only shows data stored in other locations.'; PK'9\3_$-social_activities/version.phpnu[. /** * Version details * * @package block_social_activities * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_social_activities'; // Full name of the plugin (used for diagnostics) PK'9\S  .social_activities/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_social_activities. * * @package block_social_activities * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_social_activities\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_social_activities implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\Bm  1social_activities/classes/output/blocksection.phpnu[. namespace block_social_activities\output; use core_courseformat\base as courseformat; use renderable; use section_info; use templatable; /** * Class blocksection * * @package block_social_activities * @copyright 2024 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class blocksection implements renderable, templatable { /** * The class constructor. * * @param courseformat $format the course format instance * @param section_info $section the section to render */ public function __construct( /** @var courseformat $format the course format instance. */ protected courseformat $format, /** @var section_info $section the section to render. */ protected section_info $section, ) { } /** * Export for template. * * @param \renderer_base $output * @return array */ public function export_for_template(\renderer_base $output) { $format = $this->format; $course = $format->get_course(); $section = $this->section; $sectionoutputclass = $format->get_output_classname('content\\section\\cmlist'); $sectionoutput = new $sectionoutputclass($format, $section); $cmlist = $output->render($sectionoutput); return [ 'siteid' => $course->id, 'cmlist' => $cmlist, 'sectionid' => $section->id, 'sectionname' => $format->get_section_name($section), 'sectionnum' => $section->sectionnum, 'editing' => $format->show_editor(), ]; } } PK'9\1_!g 1social_activities/templates/blocksection.mustachenu[{{! 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 . }} {{! @template block_social_activities/blocksection This mustache emulates a course single section structure inside a block. It requires to include several divs to emulate the structure of a course format. Example context (json): { "cmlist": "Sample", "siteid": 1, "sectionid": 1, "sectionname": "Sample", "sectionnum": 1, "editing": true } }}
          {{! The section list is used by the content module to init the sections.}}
          {{! The section need some bottom padding and margin for the dropzone. Otherwise the activities will cover the area. }}
          {{#editing}} {{! The section header is used as a dropzone when the section is empty.}}
           
          {{/editing}} {{{cmlist}}}
          {{#js}} {{! The block should be fast to load, we only load the editor when needed.}} {{#editing}} require(['core_courseformat/local/content'], function(component) { // We want to include both block content and block footer in the selector // because the footer has the add activity button. component.init('.block_social_activities', {}); }); {{/editing}} {{/js}} PK'9\38social_activities/UPGRADING.mdnu[# block_social_activities Upgrade notes ## 5.0 ### Removed - 'Activity' selector in social_activities block has been deleted. For more information see [MDL-83733](https://tracker.moodle.org/browse/MDL-83733) PK'9\5social_activities/tests/behat/edit_activities.featurenu[@block @block_social_activities @format_social Feature: Edit activities in social activities block In order to use social activities block As a teacher I need to add and edit activities there Background: Given the following "course" exists: | fullname | Course 1 | | shortname | C1 | | format | social | | numsections | 0 | And the following "users" exist: | username | firstname | lastname | | teacher1 | Teacher | One | | student1 | Student | One | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | @javascript Scenario: Edit name of activity in-place in social activities block Given the following "activities" exist: | activity | course | name | | forum | C1 | My forum name | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I set the field "Edit title" in the "My forum name" "activity" to "New forum name" Then I should not see "My forum name" And I should see "New forum name" And I follow "New forum name" And I should not see "My forum name" And I should see "New forum name" @javascript Scenario: Activities in social activities block can be made available but not visible on a course page Given the following config values are set as admin: | allowstealth | 1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | recent_activity | Course | C1 | course-view-* | side-pre | And the following "activity" exists: | activity | forum | | course | C1 | | name | My forum name | | section | 0 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I open "My forum name" actions menu And I choose "Availability > Make available but don't show on course page" in the open action menu Then I should see "Available but not shown on course page" in the "My forum name" "core_courseformat > Activity visibility" # Make sure that "Availability" dropdown in the edit menu has three options. And I open "My forum name" actions menu And I choose "Edit settings" in the open action menu And I expand all fieldsets And the "Availability" select box should contain "Show on course page" And the "Availability" select box should contain "Hide on course page" And the field "Availability" matches value "Make available but don't show on course page" And I press "Save and return to course" Then I should see "Available but not shown on course page" in the "My forum name" "core_courseformat > Activity visibility" And I turn editing mode off And I should see "Available but not shown on course page" in the "My forum name" "core_courseformat > Activity visibility" And I log out # Student will not see the module on the course page but can access it from other reports and blocks: When I am on the "Course 1" course page logged in as student1 Then I should not see "My forum name" in the "Social activities" "block" And I click on "My forum name" "link" in the "Recent activity" "block" And I should see "My forum name" in the ".breadcrumb" "css_element" @javascript Scenario: The move activity modal allow to move activities in the social activities block And the following "activities" exist: | activity | course | section | name | | forum | C1 | 0 | My forum name | | forum | C1 | 0 | Other forum name | | forum | C1 | 0 | Yet another forum name | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I should see "My forum name" in the "Social activities" "block" And I should see "Other forum name" in the "Social activities" "block" And I should see "Yet another forum name" in the "Social activities" "block" And "Other forum name" "activity" should appear after "My forum name" "activity" And "Yet another forum name" "activity" should appear after "Other forum name" "activity" When I open "My forum name" actions menu And I click on "Move" "link" in the "My forum name" activity And I should see "My forum name" in the "Move activity" "dialogue" And I should see "Other forum name" in the "Move activity" "dialogue" And I should see "Yet another forum name" in the "Move activity" "dialogue" And I should see "Social activities" in the "Move activity" "dialogue" And I click on "Yet another forum name" "link" in the "Move activity" "dialogue" Then I should see "My forum name" in the "Social activities" "block" And I should see "Other forum name" in the "Social activities" "block" And I should see "Yet another forum name" in the "Social activities" "block" And "Yet another forum name" "activity" should appear after "Other forum name" "activity" And "My forum name" "activity" should appear after "Yet another forum name" "activity" @javascript Scenario: Teacher can delete an activity in the social activities block Given the following "activity" exists: | activity | forum | | course | C1 | | name | My forum name | | section | 0 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I open "My forum name" actions menu And I choose "Delete" in the open action menu And I click on "Delete" "button" in the "Delete activity?" "dialogue" Then I should not see "My forum name" in the "Social activities" "block" @javascript Scenario: Teacher can duplicate an activity in the social activities block Given the following "activity" exists: | activity | forum | | course | C1 | | name | My forum name | | section | 0 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I open "My forum name" actions menu And I choose "Duplicate" in the open action menu Then I should see "My forum name" in the "Social activities" "block" And I should see "My forum name (copy)" in the "Social activities" "block" And "My forum name (copy)" "activity" should appear after "My forum name" "activity" @javascript Scenario: Teacher can move right and left an activity in the social activities block Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I open "Social forum" actions menu And "Move right" "link" should be visible And "Move left" "link" should not be visible And I choose "Move right" in the open action menu Then I open "Social forum" actions menu And "Move right" "link" should not be visible And "Move left" "link" should be visible And I choose "Move left" in the open action menu And I open "Social forum" actions menu And "Move right" "link" should be visible And "Move left" "link" should not be visible @javascript Scenario: Social activities block can have subsections Given the following "activity" exists: | activity | assign | | course | C1 | | name | Assignment name | | section | 0 | Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I click on "Add content" "button" in the ".block_social_activities .footer" "css_element" And I click on "Subsection" "link" in the ".dropdown-menu.show" "css_element" Then I should see "New subsection" in the "Social activities" "block" And I open "Assignment name" actions menu And I click on "Move" "link" in the "Assignment name" activity And I click on "New subsection" "link" in the "Move activity" "dialogue" Then I should see "Assignment name" in the "New subsection" "section" PK'9\N&&Jsocial_activities/tests/behat/behat_block_social_activities_deprecated.phpnu[. // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../../../lib/behat/behat_deprecated_base.php'); use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\ExpectationException as ExpectationException; use Behat\Mink\Exception\DriverException as DriverException; use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; /** * Behat steps in plugin block_social_activities * * @package block_social_activities * @category test * @copyright 2024 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_block_social_activities_deprecated extends behat_deprecated_base { /** * Returns the DOM node of the activity in the social activities block * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @throws ElementNotFoundException Thrown by behat_base::find * @param string $activityname The activity name * @return NodeElement */ protected function get_social_block_activity_node($activityname) { $activityname = behat_context_helper::escape($activityname); $xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., $activityname)]"; return $this->find('xpath', $xpath); } /** * Finds the element containing a specific activity in the social activity block. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @throws ElementNotFoundException * @param string $element * @param string $selectortype * @param string $activityname * @return NodeElement */ protected function get_social_block_activity_element($element, $selectortype, $activityname) { $activitynode = $this->get_social_block_activity_node($activityname); $exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '{$activityname}'"); return $this->find($selectortype, $element, $exception, $activitynode); } /** * Checks that the specified activity in the social activities block should have the specified editing icon. * * This includes items in the action menu for the item (does not require it to be open) * * You should be in the course page with editing mode turned on. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should have "(?P(?:[^"]|\\")*)" editing icon$/ * @param string $activityname * @param string $iconname */ public function activity_in_social_activities_block_should_have_editing_icon($activityname, $iconname) { $this->deprecated_message([ 'behat_block_social_activities::activity_in_social_activities_block_should_have_editing_icon is deprecated', 'Use: I should see WHATEVER in the ACTIVITYNAME "activity"', ]); $activitynode = $this->get_social_block_activity_node($activityname); $notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' . $iconname . '" editing icon', $this->getSession()); $this->find('named_partial', ['link', $iconname], $notfoundexception, $activitynode); } /** * Checks that the specified activity in the social activities block should not have the specified editing icon. * * This includes items in the action menu for the item (does not require it to be open) * * You should be in the course page with editing mode turned on. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should not have "(?P(?:[^"]|\\")*)" editing icon$/ * @param string $activityname * @param string $iconname */ public function activity_in_social_activities_block_should_not_have_editing_icon($activityname, $iconname) { $this->deprecated_message([ 'behat_block_social_activities::activity_in_social_activities_block_should_not_have_editing_icon is deprecated', 'Use: I should not see WHATEVER in the ACTIVITYNAME "activity"', ]); $activitynode = $this->get_social_block_activity_node($activityname); try { $this->find('named_partial', ['link', $iconname], false, $activitynode); throw new ExpectationException('"' . $activityname . '" has a "' . $iconname . '" editing icon when it should not', $this->getSession()); } catch (ElementNotFoundException $e) { // This is good, the menu item should not be there. return; } } /** * Clicks on the specified element of the activity. You should be in the course page with editing mode turned on. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Given /^I click on "(?P(?:[^"]|\\")*)" "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" activity in social activities block$/ * @param string $element * @param string $selectortype * @param string $activityname */ public function i_click_on_in_the_activity_in_social_activities_block($element, $selectortype, $activityname) { $this->deprecated_message([ 'behat_block_social_activities::i_click_on_in_the_activity_in_social_activities_block is deprecated', 'Use: I open ACTIVITYNAME actions menu & I choose OPTIONTEXT in the open action menu', ]); $element = $this->get_social_block_activity_element($element, $selectortype, $activityname); $element->click(); } /** * Checks that the specified activity is hidden in the social activities block. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should be hidden$/ * @param string $activityname */ public function activity_in_social_activities_block_should_be_hidden($activityname) { $this->deprecated_message([ 'behat_block_social_activities::activity_in_social_activities_block_should_be_hidden is deprecated', 'Use: I should see "Hidden from students" in the "ACTIVITYNAME" "core_courseformat > Activity visibility"', ]); $activitynode = $this->get_social_block_activity_node($activityname); $exception = new ExpectationException('"' . $activityname . '" is not hidden', $this->getSession()); $this->find('named_partial', ['badge', get_string('hiddenfromstudents')], $exception, $activitynode); } /** * Checks that the specified activity is hidden in the social activities block. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should be available but hidden from course page$/ * @param string $activityname */ public function activity_in_social_activities_block_should_be_available_but_hidden_from_course_page($activityname) { $this->deprecated_message([ 'behat_block_social_activities::activity_in_social_activities_block_should_be_available_but_hidden_from_course_page is deprecated', 'Use: I should see "Available but not shown on course page" in the "ACTIVITYNAME" "core_courseformat > Activity visibility"', ]); $activitynode = $this->get_social_block_activity_node($activityname); $exception = new ExpectationException('"' . $activityname . '" is not hidden but available', $this->getSession()); $this->find('named_partial', ['badge', get_string('hiddenoncoursepage')], $exception, $activitynode); } /** * Opens an activity actions menu in the social activities block if it is not already opened. * * @todo MDL-78077 This will be deleted in Moodle 6.0. * @deprecated since 5.0 * * @Given /^I open "(?P(?:[^"]|\\")*)" actions menu in social activities block$/ * @throws DriverException The step is not available when Javascript is disabled * @param string $activityname */ public function i_open_actions_menu_in_social_activities_block($activityname) { $this->deprecated_message([ 'behat_block_social_activities::i_open_actions_menu_in_social_activities_block is deprecated', 'Use: I open "ACTIVITYNAME" actions menu', ]); $activityname = behat_context_helper::escape($activityname); $xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., $activityname)]"; $this->execute('behat_action_menu::i_open_the_action_menu_in', [$xpath, 'xpath_element']); } } PK'9\=?-social_activities/block_social_activities.phpnu[. /** * Social activities block. * * @package block_social_activities * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_social_activities extends block_base { function init(){ $this->title = get_string('pluginname', 'block_social_activities'); } function applicable_formats() { return array('course-view-social' => true); } function get_content() { if ($this->content !== NULL) { return $this->content; } $this->content = new stdClass(); $this->content->text = ''; $this->content->footer = ''; if (empty($this->instance)) { return $this->content; } $course = $this->page->course; course_create_sections_if_missing($course, 0); $format = course_get_format($course); $modinfo = $format->get_modinfo(); $section = $modinfo->get_section_info(0); if ($format->supports_ajax()) { include_course_ajax($course); } $courserenderer = $format->get_renderer($this->page); $output = new block_social_activities\output\blocksection($format, $section); $this->content->text = $courserenderer->render($output); $this->content->footer = $courserenderer->course_section_add_cm_control( course: $course, section: 0, sectionreturn: null, displayoptions: ['inblock' => true], ); return $this->content; } } PK'9\b5 lp/styles.cssnu[.block_lp.block .content h3 { padding: 0; text-transform: none; } .block_lp .sub-content { padding: 0 15px; } .block_lp ul { list-style: none; margin: 0; } .block_lp ul .more { padding-top: 10px; } PK'9\plp/db/access.phpnu[. /** * Block LP capabilities. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( // Whether or not the user can add the block. 'block/lp:addinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), // Whether or not the user can add the block on their dashboard. 'block/lp:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ) ), ); PK'9\lp/lang/en/block_lp.phpnu[. /** * Block LP language strings. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $string['competenciestoreview'] = 'Competencies to review'; $string['lp:addinstance'] = 'Add a new learning plans block'; $string['lp:myaddinstance'] = 'Add a new learning plans block to Dashboard'; $string['myplans'] = 'My plans'; $string['noactiveplans'] = 'No active plans at the moment.'; $string['planstoreview'] = 'Plans to review'; $string['pluginname'] = 'Learning plans'; $string['viewmore'] = 'View more...'; $string['viewotherplans'] = 'View other plans...'; $string['privacy:metadata'] = 'The Learning plans block only shows data stored in other locations.'; PK'9\00lp/version.phpnu[. /** * Block LP version file. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; $plugin->requires = 2025040800; $plugin->component = 'block_lp'; $plugin->dependencies = [ 'tool_lp' => ANY_VERSION, ]; PK'9\yOPlp/plans_to_review.phpnu[. /** * Competencies to review page. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../../config.php'); require_login(null, false); if (isguestuser()) { throw new require_login_exception('Guests are not allowed here.'); } $toreviewstr = get_string('planstoreview', 'block_lp'); $url = new moodle_url('/blocks/lp/plans_to_review.php'); $PAGE->set_context(context_user::instance($USER->id)); $PAGE->set_url($url); $PAGE->set_title($toreviewstr); $PAGE->set_pagelayout('standard'); $PAGE->navbar->add($toreviewstr, $url); $output = $PAGE->get_renderer('block_lp'); echo $output->header(); echo $output->heading($toreviewstr); $page = new \block_lp\output\plans_to_review_page(); echo $output->render($page); echo $output->footer(); PK'9\y//lp/competencies_to_review.phpnu[. /** * Competencies to review page. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../../config.php'); require_login(null, false); if (isguestuser()) { throw new require_login_exception('Guests are not allowed here.'); } $toreviewstr = get_string('competenciestoreview', 'block_lp'); $url = new moodle_url('/blocks/lp/competencies_to_review.php'); $PAGE->set_context(context_user::instance($USER->id)); $PAGE->set_url($url); $PAGE->set_title($toreviewstr); $PAGE->set_pagelayout('standard'); $PAGE->navbar->add($toreviewstr, $url); $output = $PAGE->get_renderer('block_lp'); echo $output->header(); echo $output->heading($toreviewstr); $page = new \block_lp\output\competencies_to_review_page(); echo $output->render($page); echo $output->footer(); PK'9\lp/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_lp. * * @package block_lp * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_lp\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_lp implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\ESSlp/classes/output/renderer.phpnu[. /** * Block LP renderer. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_lp\output; defined('MOODLE_INTERNAL') || die(); use plugin_renderer_base; use renderable; /** * Block LP renderer class. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends plugin_renderer_base { /** * Defer to template. * @param renderable $page * @return string */ public function render_competencies_to_review_page(renderable $page) { $data = $page->export_for_template($this); return parent::render_from_template('block_lp/competencies_to_review_page', $data); } /** * Defer to template. * @param renderable $page * @return string */ public function render_plans_to_review_page(renderable $page) { $data = $page->export_for_template($this); return parent::render_from_template('block_lp/plans_to_review_page', $data); } /** * Defer to template. * @param renderable $summary * @return string */ public function render_summary(renderable $summary) { $data = $summary->export_for_template($this); return parent::render_from_template('block_lp/summary', $data); } } PK'9\p''lp/classes/output/summary.phpnu[. /** * Summary renderable. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_lp\output; defined('MOODLE_INTERNAL') || die(); use core_competency\api; use core_competency\external\competency_exporter; use core_competency\external\plan_exporter; use core_competency\external\user_competency_exporter; use core_user\external\user_summary_exporter; use core_competency\plan; use core_competency\url; use renderable; use renderer_base; use templatable; use required_capability_exception; /** * Summary renderable class. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class summary implements renderable, templatable { /** @var array Active plans. */ protected $activeplans = array(); /** @var array Competencies to review. */ protected $compstoreview = array(); /** @var array Plans to review. */ protected $planstoreview = array(); /** @var array Plans. */ protected $plans = array(); /** @var stdClass The user. */ protected $user; /** * Constructor. * @param stdClass $user The user. */ public function __construct($user = null) { global $USER; if (!$user) { $user = $USER; } $this->user = $user; // Get the plans. try { $this->plans = api::list_user_plans($this->user->id); } catch (required_capability_exception $e) { $this->plans = []; } // Get the competencies to review. $this->compstoreview = api::list_user_competencies_to_review(0, 3); // Get the plans to review. $this->planstoreview = api::list_plans_to_review(0, 3); } public function export_for_template(renderer_base $output) { $plans = array(); foreach ($this->plans as $plan) { if (count($plans) >= 3) { break; } if ($plan->get('status') == plan::STATUS_ACTIVE) { $plans[] = $plan; } } $activeplans = array(); foreach ($plans as $plan) { $planexporter = new plan_exporter($plan, array('template' => $plan->get_template())); $activeplans[] = $planexporter->export($output); } $compstoreview = array(); foreach ($this->compstoreview['competencies'] as $compdata) { $ucexporter = new user_competency_exporter($compdata->usercompetency, array('scale' => $compdata->competency->get_scale())); $compexporter = new competency_exporter($compdata->competency, array('context' => $compdata->competency->get_context())); $userexporter = new user_summary_exporter($compdata->user); $compstoreview[] = array( 'usercompetency' => $ucexporter->export($output), 'competency' => $compexporter->export($output), 'user' => $userexporter->export($output), ); } $planstoreview = array(); foreach ($this->planstoreview['plans'] as $plandata) { $planexporter = new plan_exporter($plandata->plan, array('template' => $plandata->template)); $userexporter = new user_summary_exporter($plandata->owner); $planstoreview[] = array( 'plan' => $planexporter->export($output), 'user' => $userexporter->export($output), ); } $data = array( 'hasplans' => !empty($this->plans), 'hasactiveplans' => !empty($activeplans), 'hasmoreplans' => count($this->plans) > count($activeplans), 'activeplans' => $activeplans, 'compstoreview' => $compstoreview, 'hascompstoreview' => $this->compstoreview['count'] > 0, 'hasmorecompstoreview' => $this->compstoreview['count'] > 3, 'planstoreview' => $planstoreview, 'hasplanstoreview' => $this->planstoreview['count'] > 0, 'hasmoreplanstoreview' => $this->planstoreview['count'] > 3, 'plansurl' => url::plans($this->user->id)->out(false), 'pluginbaseurl' => (new \moodle_url('/blocks/lp'))->out(false), 'userid' => $this->user->id, ); return $data; } /** * Returns whether there is content in the summary. * * @return boolean */ public function has_content() { return !empty($this->plans) || $this->planstoreview['count'] > 0 || $this->compstoreview['count'] > 0; } } PK'9\*_;.  1lp/classes/output/competencies_to_review_page.phpnu[. /** * Competencies to review renderable. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_lp\output; defined('MOODLE_INTERNAL') || die(); use renderable; use templatable; use renderer_base; use stdClass; use moodle_url; use core_competency\api; use core_competency\external\competency_exporter; use core_competency\external\user_competency_exporter; use core_user\external\user_summary_exporter; /** * Competencies to review renderable class. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class competencies_to_review_page implements renderable, templatable { /** @var array Competencies to review. */ protected $compstoreview; /** * Constructor. */ public function __construct() { $this->compstoreview = api::list_user_competencies_to_review(0, 1000); } /** * Export the data. * * @param renderer_base $output * @return stdClass */ public function export_for_template(renderer_base $output) { $data = new stdClass(); $compstoreview = array(); foreach ($this->compstoreview['competencies'] as $compdata) { $ucexporter = new user_competency_exporter($compdata->usercompetency, array('scale' => $compdata->competency->get_scale())); $compexporter = new competency_exporter($compdata->competency, array('context' => $compdata->competency->get_context())); $userexporter = new user_summary_exporter($compdata->user); $compstoreview[] = array( 'usercompetency' => $ucexporter->export($output), 'competency' => $compexporter->export($output), 'user' => $userexporter->export($output), ); } $data = array( 'competencies' => $compstoreview, 'pluginbaseurl' => (new moodle_url('/blocks/lp'))->out(false), ); return $data; } } PK'9\utН *lp/classes/output/plans_to_review_page.phpnu[. /** * Plans to review renderable. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_lp\output; defined('MOODLE_INTERNAL') || die(); use renderable; use templatable; use renderer_base; use stdClass; use moodle_url; use core_competency\api; use core_competency\external\plan_exporter; use core_user\external\user_summary_exporter; /** * Plans to review renderable class. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class plans_to_review_page implements renderable, templatable { /** @var array Plans to review. */ protected $planstoreview; /** * Constructor. */ public function __construct() { $this->planstoreview = api::list_plans_to_review(0, 1000); } /** * Export the data. * * @param renderer_base $output * @return stdClass */ public function export_for_template(renderer_base $output) { $data = new stdClass(); $planstoreview = array(); foreach ($this->planstoreview['plans'] as $plandata) { $planexporter = new plan_exporter($plandata->plan, array('template' => $plandata->template)); $userexporter = new user_summary_exporter($plandata->owner); $planstoreview[] = array( 'plan' => $planexporter->export($output), 'user' => $userexporter->export($output), ); } $data = array( 'plans' => $planstoreview, 'pluginbaseurl' => (new moodle_url('/blocks/lp'))->out(false), ); return $data; } } PK'9\X X X lp/templates/summary.mustachenu[{{! 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 . }} {{! Summary. Classes required for JS: * None Data attibutes required for JS: * None Context variables required for this template: * hasplans * hasactiveplans * activeplans * hasmoreplans * hascompstoreview * compstoreview * hasmorecompstoreview * hasplanstoreview * planstoreview * hasmoreplanstoreview }}
          {{#hasplans}}

          {{#str}}myplans, block_lp{{/str}}

          {{#hasactiveplans}} {{/hasactiveplans}} {{^hasactiveplans}}

          {{#str}}noactiveplans, block_lp{{/str}} {{#str}}viewotherplans, block_lp{{/str}}

          {{/hasactiveplans}}
          {{/hasplans}} {{#hascompstoreview}}

          {{#str}}competenciestoreview, block_lp{{/str}}

          {{/hascompstoreview}} {{#hasplanstoreview}}

          {{#str}}planstoreview, block_lp{{/str}}

          {{/hasplanstoreview}}
          PK'9\2#*lp/templates/plans_to_review_page.mustachenu[{{! 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 . }} {{! Plans to review. Classes required for JS: * - Data attributes required for JS: * - Context variables required for this template: * plans }}
          {{#plans}} {{/plans}}
          {{#str}}planname, tool_lp{{/str}} {{#str}}user{{/str}} {{#str}}reviewstatus, tool_lp{{/str}}
          {{{plan.name}}} {{user.fullname}} {{plan.statusname}}
          PK'9\$1lp/templates/competencies_to_review_page.mustachenu[{{! 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 . }} {{! Competencies to review. Classes required for JS: * - Data attributes required for JS: * - Context variables required for this template: * competencies }}
          {{#competencies}} {{/competencies}}
          {{#str}}shortname, tool_lp{{/str}} {{#str}}user{{/str}} {{#str}}reviewstatus, tool_lp{{/str}}
          {{{competency.shortname}}} {{user.fullname}} {{usercompetency.statusname}}
          PK'9\uPlp/upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This file describes API changes in the lp block code. === 3.7 === * The 'block/lp:view' capability has been removed. It has never been used in code.PK'9\Ilp/tests/generator/lib.phpnu[. /** * Learning plans block data generator class. * * @package block_lp * @category test * @copyright 2021 Sara Arjona * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_lp_generator extends testing_block_generator { } PK'9\"lp/tests/lp_test.phpnu[. namespace block_lp; use advanced_testcase; use block_lp; use context_course; /** * PHPUnit block_lp tests * * @package block_lp * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_lp */ final class lp_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_lp.php'); parent::setUpBeforeClass(); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_lp(); // If blogs advanced feature is enabled, the method should return true. set_config('enabled', true, 'core_competency'); $this->assertTrue($block->can_block_be_added($page)); // However, if the blogs advanced feature is disabled, the method should return false. set_config('enabled', false, 'core_competency'); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\7pE E lp/block_lp.phpnu[. /** * Block LP main file. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Block LP class. * * @package block_lp * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_lp extends block_base { /** * Applicable formats. * * @return array */ public function applicable_formats() { return array('site' => true, 'course' => true, 'my' => true); } /** * Init. * * @return void */ public function init() { $this->title = get_string('pluginname', 'block_lp'); } /** * Get content. * * @return stdClass */ public function get_content() { if (isset($this->content)) { return $this->content; } $this->content = new stdClass(); if (!get_config('core_competency', 'enabled')) { return $this->content; } // Block needs a valid, non-guest user to be logged-in in order to display the user's learning plans. if (isloggedin() && !isguestuser()) { $summary = new \block_lp\output\summary(); if (!$summary->has_content()) { return $this->content; } $renderer = $this->page->get_renderer('block_lp'); $this->content->text = $renderer->render($summary); $this->content->footer = ''; } return $this->content; } /** * This block shouldn't be added to a page if the competencies advanced feature is disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { return get_config('core_competency', 'enabled'); } } PK'9\w]]accessreview/styles.cssnu[.block_accessreview_success, .block_accessreview.block_accessreview_success.hasinfo { color: #1e451e; background: #eff5ef; box-shadow: 0 0 2px 2px #619a61; } .block_accessreview_danger, .block_accessreview.block_accessreview_danger.hasinfo { color: #6e211e; background: #fdf7f7; box-shadow: 0 0 2px 2px #da6960; } .block_accessreview_warning, .block_accessreview.block_accessreview_warning.hasinfo { color: #694b21; background: #fdf2e3; box-shadow: 0 0 2px 2px #c97a0e; } .block_accessreview_table { border-bottom: 1px solid; border-color: #eee8e8; } .block_accessreview_links { display: flex; flex-flow: row; flex-wrap: wrap; justify-content: center; } .block_accessreview_link { padding-left: 10px; padding-right: 10px; } .block_accessreview_analysisbutton { text-align: center; } PK'9\$e]accessreview/db/services.phpnu[. /** * External service definitions for the accessreview block. * * @package block_accessreview * @author Max Larkin * @copyright 2020 Brickfield Education Labs * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $functions = [ 'block_accessreview_get_module_data' => [ 'classname' => 'block_accessreview\external\get_module_data', 'methodname' => 'execute', 'description' => 'Gets error data for course modules.', 'type' => 'read', 'ajax' => true, 'capabilities' => 'block/accessreview:view', ], 'block_accessreview_get_section_data' => [ 'classname' => 'block_accessreview\external\get_section_data', 'methodname' => 'execute', 'description' => 'Gets error data for course sections.', 'type' => 'read', 'ajax' => true, 'capabilities' => 'block/accessreview:view', ] ]; PK'9\4{accessreview/db/access.phpnu[. /** * accessreview capabilities definition * * @package block_accessreview * @copyright 2019 Karen Holland LTS.ie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/accessreview:addinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), 'block/accessreview:view' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), ), ); PK'9\+ + +accessreview/lang/en/block_accessreview.phpnu[. /** * Simple block language strings * * @package block_accessreview * @copyright 2019 Karen Holland LTS.ie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $string['accessreview:addinstance'] = 'Add an accessibility review block'; $string['accessreview:view'] = 'View the accessibility review'; $string['checktypes'] = 'Types'; $string['errors'] = 'Errors'; $string['errordisplay'] = 'Display errors in this format'; $string['errorssummary'] = 'Accessibility review'; $string['frown'] = 'Errors found, sorry!'; $string['nodata'] = 'No accessibility results data was found.'; $string['pluginname'] = 'Accessibility review'; $string['privacy:metadata:preference:block_accessreviewtogglestate'] = 'The user\'s preference for hiding or showing the course accessibility highlighting.'; $string['privacy:togglestateoff'] = 'The current preference for the course accessibility highlighting is off.'; $string['privacy:togglestateon'] = 'The current preference for the course accessibility highlighting is on.'; $string['showbackground'] = 'Show background colouring only'; $string['showboth'] = 'Show both background colour and status icons'; $string['showicon'] = 'Display smiley'; $string['showicons'] = 'Show count icons only'; $string['showint'] = 'Display amount'; $string['showpercent'] = 'Display percentage'; $string['smile'] = '0 errors, congratulations!'; $string['status:successalt'] = 'Passed'; $string['status:success'] = 'Passed'; $string['status:errors'] = 'Failed - error count: {$a->errorCount}'; $string['status:errorsalt'] = 'Errors'; $string['toggleaccessreview'] = 'Toggle highlighting'; $string['toolpage'] = 'Toolkit page to show'; $string['totalerrors'] = 'Total errors: {$a}
          (total excludes course settings)'; $string['viewreportalt'] = 'View accessibility toolkit'; $string['viewreport'] = 'View'; $string['downloadreportalt'] = 'Download accessibility summary report'; $string['downloadreport'] = 'Download'; $string['togglealt'] = 'Toggle accessibility heatmap'; $string['toggle'] = 'Toggle highlighting'; $string['whattoshow'] = 'What to show on course page'; PK'9\Qaccessreview/pix/f/image.svgnu[ PK'9\K)accessreview/pix/f/video.svgnu[ PK'9\_,$accessreview/pix/f/table.svgnu[ PK'9\zSSaccessreview/pix/f/text.svgnu[ PK'9\>b9accessreview/pix/f/layout.svgnu[ PK'9\,55accessreview/pix/f/link.svgnu[ PK'9\cVaccessreview/pix/f/media.svgnu[ PK'9\bZaccessreview/pix/f/find.svgnu[ PK'9\^l9accessreview/pix/f/form.svgnu[ PK'9\ )  accessreview/pix/f/pdf.svgnu[ PK'9\accessreview/pix/smile.svgnu[ PK'9\_Ҙaccessreview/pix/frown.svgnu[ PK'9\faccessreview/pix/t/pass.svgnu[ PK'9\K*$$accessreview/pix/t/fail.svgnu[ PK'9\%% accessreview/pix/errorsfound.svgnu[ PK'9\x+5Sk k accessreview/lib.phpnu[. /** * Lib library of functions. * * @package block_accessreview * @copyright 2019 Karen Holland LTS.ie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Get icon mapping for font-awesome. */ function block_accessreview_get_fontawesome_icon_map() { return [ 'block_accessreview:smile' => 'fa-regular fa-smile', 'block_accessreview:frown' => 'fa-regular fa-face-frown', 'block_accessreview:errorsfound' => 'fa-ban', 'block_accessreview:f/find' => 'fa-magnifying-glass', 'block_accessreview:f/form' => 'fa-list-check', 'block_accessreview:f/image' => 'fa-image', 'block_accessreview:f/layout' => 'fa-table-cells-large', 'block_accessreview:f/link' => 'fa-link', 'block_accessreview:f/media' => 'fa-photo-film', 'block_accessreview:f/pdf' => 'fa-regular fa-file-pdf', 'block_accessreview:f/table' => 'fa-table', 'block_accessreview:f/text' => 'fa-font', 'block_accessreview:f/video' => 'fa-regular fa-file-video', 'block_accessreview:t/fail' => 'fa-xmark', 'block_accessreview:t/pass' => 'fa-check', ]; } /** * Define preferences which may be set via the core_user_set_user_preferences external function. * * @uses core_user::is_current_user * * @return array[] */ function block_accessreview_user_preferences(): array { return [ 'block_accessreviewtogglestate' => [ 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'choices' => [0, 1], 'permissioncallback' => [core_user::class, 'is_current_user'], ], ]; } PK'9\OOaccessreview/version.phpnu[. /** * Simple block version identification * * @package block_accessreview * @copyright 2019 Karen Holland LTS.ie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'block_accessreview'; $plugin->version = 2025041400; $plugin->requires = 2025040800; $plugin->dependencies = [ 'tool_brickfield' => ANY_VERSION, ]; PK'9\  1accessreview/classes/external/get_module_data.phpnu[. namespace block_accessreview\external; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use tool_brickfield\manager; /** * Web service to fetch module data. * * @package block_accessreview * @copyright 2020 onward Brickfield Education Labs Ltd, https://www.brickfield.ie * @author 2020 Max Larkin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class get_module_data extends external_api { /** * Describes the parameters. * * @return external_function_parameters */ public static function execute_parameters() { return new external_function_parameters([ 'courseid' => new external_value(PARAM_INT, 'The course id to obtain results for.', VALUE_REQUIRED), ]); } /** * Execute the service. * * @param int $courseid * @return array */ public static function execute(int $courseid) { [ 'courseid' => $courseid, ] = self::validate_parameters(self::execute_parameters(), [ 'courseid' => $courseid, ]); $context = \context_course::instance($courseid); self::validate_context($context); require_capability('block/accessreview:view', $context); return array_values(manager::get_cm_summary_for_course($courseid)); } /** * Describes the return structure of the service.. * * @return external_multiple_structure */ public static function execute_returns() { return new external_multiple_structure( new external_single_structure( [ 'cmid' => new external_value(PARAM_INT, 'ID'), 'numerrors' => new external_value(PARAM_INT, 'Number of errors.'), 'numchecks' => new external_value(PARAM_INT, 'Number of checks.'), ] ) ); } } PK'9\X? 2accessreview/classes/external/get_section_data.phpnu[. namespace block_accessreview\external; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use tool_brickfield\manager; /** * Web service to fetch section data. * * @package block_accessreview * @copyright 2020 onward Brickfield Education Labs Ltd, https://www.brickfield.ie * @author 2020 Max Larkin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class get_section_data extends external_api { /** * Describes the parameters. * * @return external_function_parameters */ public static function execute_parameters() { return new external_function_parameters([ 'courseid' => new external_value(PARAM_INT, 'The course id to obtain results for.', VALUE_REQUIRED), ]); } /** * Execute the service. * * @param int $courseid * @return array */ public static function execute($courseid) { [ 'courseid' => $courseid, ] = self::validate_parameters(self::execute_parameters(), [ 'courseid' => $courseid, ]); $context = \context_course::instance($courseid); self::validate_context($context); require_capability('block/accessreview:view', $context); return array_values(manager::get_section_summary_for_course($courseid)); } /** * Describes the return structure of the service.. * * @return external_multiple_structure */ public static function execute_returns() { return new external_multiple_structure( new external_single_structure( [ 'section' => new external_value(PARAM_INT, 'ID'), 'numerrors' => new external_value(PARAM_INT, 'Number of errors.'), 'numchecks' => new external_value(PARAM_INT, 'Number of checks.'), ] ) ); } } PK'9\> > )accessreview/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_accessreview. * * @package block_accessreview * @copyright 2020 Brickfield Education Labs, www.brickfield.ie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_accessreview\privacy; use \core_privacy\local\metadata\collection; defined('MOODLE_INTERNAL') || die(); /** * The accessreview block stores a user preference data. * * @copyright 2020 Brickfield Education Labs, www.brickfield.ie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements // This plugin has data. \core_privacy\local\metadata\provider, // This plugin has some sitewide user preferences to export. \core_privacy\local\request\user_preference_provider { /** The user preference for the toggle state. */ const TOGGLE_STATE = 'block_accessreviewtogglestate'; /** * Returns meta data about this system. * * @param collection $items The initialised item collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $items): collection { $items->add_user_preference(self::TOGGLE_STATE, 'privacy:metadata:preference:block_accessreviewtogglestate'); return $items; } /** * Store all user preferences for the plugin. * * @param int $userid The userid of the user whose data is to be exported. */ public static function export_user_preferences(int $userid) { $togglestate = get_user_preferences(self::TOGGLE_STATE, null, $userid); if (isset($togglestate)) { $preferencestring = get_string('privacy:togglestateoff', 'block_accessreview'); if ($togglestate == true) { $preferencestring = get_string('privacy:togglestateon', 'block_accessreview'); } \core_privacy\local\request\writer::export_user_preference( 'block_accessreview', self::TOGGLE_STATE, ($togglestate) ? 'On' : 'Off', $preferencestring ); } } } PK'9\aR--#accessreview/block_accessreview.phpnu[. use tool_brickfield\accessibility; use tool_brickfield\analysis; use tool_brickfield\area_base; use tool_brickfield\local\tool\filter; use tool_brickfield\manager; use tool_brickfield\registration; use tool_brickfield\scheduler; use tool_brickfield\sitedata; /** * Definition of the accessreview block. * * @package block_accessreview * @copyright 2019 Karen Holland LTS.ie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_accessreview extends block_base { /** * Sets the block title. */ public function init(): void { $this->title = get_string('errorssummary', 'block_accessreview'); } /** * Defines where the block can be added. * * @return array */ public function applicable_formats(): array { return [ 'course-view' => true, 'site' => true, 'mod' => false, 'my' => false, ]; } /** * Controls global configurability of block. * * @return bool */ public function has_config(): bool { return true; } /** * Controls whether multiple block instances are allowed. * * @return bool */ public function instance_allow_multiple(): bool { return false; } /** * Creates the block's main content * * @return string|stdClass */ public function get_content() { global $COURSE, $OUTPUT; // If Brickfield accessibility toolkit has been disabled, do nothing. if (!accessibility::is_accessibility_enabled()) { return ''; } if (isset($this->content)) { return $this->content; } $this->content = new stdClass; $this->content->text = ''; // Check to see user can view/use the accessmap. $context = context_course::instance($COURSE->id); if (!isloggedin() || isguestuser() || !has_capability('block/accessreview:view', $context)) { return $this->content; } // Check for valid registration. if (!(new registration())->toolkit_is_active()) { $this->content->text = manager::registration_message(); } else if (scheduler::is_course_analyzed($COURSE->id)) { // Build error data table. $table = new html_table(); $table->head = [ get_string('checktypes', 'block_accessreview'), get_string('errors', 'block_accessreview') ]; $table->align = ['left', 'center']; $tabledata = $this->get_table_data($COURSE->id); // Handling no data. if ($tabledata === null) { $this->content->text = get_string('nodata', 'block_accessreview'); return $this->content; } $table->data = $tabledata; $table->attributes['class'] = 'generaltable table-sm block_accessreview_table table table-hover'; $this->content->text .= html_writer::table($table, true); // Check for compatible course formats for highlighting. $showhighlighting = false; switch ($COURSE->format) { case accessibility::TOOL_BRICKFIELD_FORMAT_TOPIC: case accessibility::TOOL_BRICKFIELD_FORMAT_WEEKLY: $showhighlighting = true; break; } // Toggle overlay link. $toggle = ($showhighlighting) ? $this->get_toggle_link() : ''; // Report download link. $download = $this->get_download_link($context); // Report view link. $view = $this->get_report_link($context); $this->content->text .= html_writer::tag('div', $toggle . $view . $download, [ 'class' => 'block_accessreview_links' ] ); if ($showhighlighting) { // Setting up AMD module. $whattoshow = get_config('block_accessreview', 'whattoshow'); $toggled = get_user_preferences('block_accessreviewtogglestate', true); $arguments = [$toggled, $whattoshow, $COURSE->id]; $this->page->requires->js_call_amd('block_accessreview/module', 'init', $arguments); } } else if (scheduler::is_course_in_schedule($COURSE->id)) { // Display a message that the course is awaiting analysis. $this->content->text = get_string('schedule:scheduled', manager::PLUGINNAME); } else if (!analysis::is_enabled()) { $this->content->text = get_string('analysistypedisabled', manager::PLUGINNAME); } else { // Display a button to request analysis. $this->content->text = get_string('schedule:blocknotscheduled', manager::PLUGINNAME, manager::get_helpurl()); $button = new single_button( new moodle_url(accessibility::get_plugin_url(), ['action' => 'requestanalysis', 'courseid' => $COURSE->id]), get_string('schedule:requestanalysis', manager::PLUGINNAME), 'post', single_button::BUTTON_PRIMARY, ['class' => 'block_accessreview_analysisbutton']); $this->content->text .= html_writer::tag('div', $OUTPUT->render($button), ['class' => 'block_accessreview_analysisbutton']); } return $this->content; } /** * This block shouldn't be added to a page if the accessibility tools setting is disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { return accessibility::is_accessibility_enabled(); } /** * Fetches and groups the relevent error data for the table to display. * @param int $courseid The ID of the course. * @return array The data required by the table. * @throws coding_exception * @throws moodle_exception */ protected function get_table_data($courseid): array { global $OUTPUT; $datafilters = new filter($courseid, 0); $errordisplay = get_config('block_accessreview', 'errordisplay'); $summarydata = (new sitedata())->get_checkgroup_data($datafilters); $data = []; $count = 0; for ($i = 1; $count < $summarydata[0]->groupcount; $i++) { if (isset($summarydata[0]->{'componentlabel' . $i})) { $data[$i] = $summarydata[0]->{'errorsvalue' . $i}; $count++; } } $files = [ 'form' => '', 'image' => '231/', 'layout' => '234/', 'link' => '237/', 'media' => '240/', 'table' => '243/', 'text' => '246/', ]; // Populating table data. $tabledata = []; foreach ($data as $key => $total) { // If the total is empty it means there is no results yet in the table. if ($total === null) { continue; } $type = area_base::checkgroup_name($key); // Error display data. $display = $total; // Icons. $typeicon = $OUTPUT->pix_icon('f/' . $type, '', 'block_accessreview'); if ($errordisplay == 'showicon') { $thistype = $total == 0 ? 'smile' : 'frown'; $display = $OUTPUT->pix_icon($thistype, get_string($thistype, 'block_accessreview'), 'block_accessreview' ); } else if ($errordisplay == 'showpercent') { $display = round(($total), 1) . '%'; } $tabledata[] = [$typeicon . get_string('checktype:' . $type, manager::PLUGINNAME), $display]; } return $tabledata; } /** * Get the link to toggle the heatmap. * * @return string * @throws coding_exception */ protected function get_toggle_link(): string { global $OUTPUT; if (get_user_preferences('block_accessreviewtogglestate')) { $icon = 't/hide'; } else { $icon = 't/show'; } // Toggle overlay link. return html_writer::link( '#', $OUTPUT->pix_icon($icon, get_string('togglealt', 'block_accessreview'), 'moodle', ['class' => 'icon-accessmap']), [ 'title' => get_string('togglealt', 'block_accessreview'), 'style' => 'cursor: pointer;', 'id' => 'toggle-accessmap', 'class' => 'block_accessreview_link', ] ); } /** * Get the link to download a report for the specified context. * * @param context $context * @return string * @throws coding_exception * @throws moodle_exception */ protected function get_download_link(context $context): string { global $OUTPUT, $COURSE; if (has_capability(accessibility::get_capability_name('viewcoursetools'), $context)) { return html_writer::link( new moodle_url(accessibility::get_plugin_url(), [ 'courseid' => $COURSE->id, 'tab' => 'printable', 'target' => 'pdf', ] ), $OUTPUT->pix_icon('a/download_all', get_string('downloadreportalt', 'block_accessreview')), [ 'title' => get_string('downloadreportalt', 'block_accessreview'), 'class' => 'block_accessreview_link download-accessmap', ] ); } else { return ''; } } /** * Get the report link for the specified context. * * @param context $context * @return string * @throws coding_exception * @throws dml_exception * @throws moodle_exception */ protected function get_report_link(context $context): string { global $OUTPUT, $COURSE; if (has_capability(accessibility::get_capability_name('viewcoursetools'), $context)) { return html_writer::link( new moodle_url(accessibility::get_plugin_url(), [ 'courseid' => $COURSE->id, 'tab' => get_config('block_accessreview', 'toolpage'), ] ), $OUTPUT->pix_icon('f/find', get_string('viewreportalt', 'block_accessreview'), 'block_accessreview'), [ 'title' => get_string('viewreportalt', 'block_accessreview'), 'class' => 'block_accessreview_link report-accessmap', ] ); } else { return ''; } } } PK'9\ ,&accessreview/templates/status.mustachenu[{{! 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 . }} {{! @template block_accessreview/status Accessibility review result information. Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * classList - array of block_accessreview classes * resultPassed - boolean indicating that the item is correct * passRate - object containing metrics * errorCount - the number of failures * checkCount - the number of items checked * failureRate - the percentage of items failing Example context (json): { "classList": "block_accessreview_danger", "passRate": { "checkCount": "60", "errorCount": "12", "failureRate": "20" } } }}
          {{#resultPassed}} {{#pix}}t/pass, block_accessreview, {{#str}}status:successalt, block_accessreview{{/str}}{{/pix}} {{#str}}status:success, block_accessreview, { "errorCount": {{passRate.errorCount}}, "checkCount": {{passRate.checkCount}}, "failureRate": {{passRate.failureRate}} }{{/str}} {{/resultPassed}} {{^resultPassed}} {{#pix}}t/fail, block_accessreview, {{#str}}status:errorsalt, block_accessreview{{/str}}{{/pix}} {{#str}}status:errors, block_accessreview, { "errorCount": {{passRate.errorCount}}, "checkCount": {{passRate.checkCount}}, "failureRate": {{passRate.failureRate}} }{{/str}} {{/resultPassed}}
          PK'9\[^O##accessreview/amd/src/module.jsnu[// 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 . /** * Manager for the accessreview block. * * @module block_accessreview/module * @author Max Larkin * @copyright 2020 Brickfield Education Labs * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {call as fetchMany} from 'core/ajax'; import * as Templates from 'core/templates'; import {exception as displayError} from 'core/notification'; /** * The number of colours used to represent the heatmap. (Indexed on 0.) * @type {number} */ const numColours = 2; /** * The toggle state of the heatmap. * @type {boolean} */ let toggleState = true; /** * Renders the HTML template onto a particular HTML element. * @param {HTMLElement} element The element to attach the HTML to. * @param {number} errorCount The number of errors on this module/section. * @param {number} checkCount The number of checks triggered on this module/section. * @param {String} displayFormat * @param {Number} minViews * @param {Number} viewDelta * @returns {Promise} */ const renderTemplate = (element, errorCount, checkCount, displayFormat, minViews, viewDelta) => { // Calculate a weight? const weight = parseInt((errorCount - minViews) / viewDelta * numColours); const context = { resultPassed: !errorCount, classList: '', passRate: { errorCount, checkCount, failureRate: Math.round(errorCount / checkCount * 100), }, }; if (!element) { return Promise.resolve(); } const elementClassList = ['block_accessreview']; if (context.resultPassed) { elementClassList.push('block_accessreview_success'); } else if (weight) { elementClassList.push('block_accessreview_danger'); } else { elementClassList.push('block_accessreview_warning'); } const showIcons = (displayFormat == 'showicons') || (displayFormat == 'showboth'); const showBackground = (displayFormat == 'showbackground') || (displayFormat == 'showboth'); if (showBackground && !showIcons) { // Only the background is displayed. // No need to display the template. // Note: The case where both the background and icons are shown is handled later to avoid jankiness. element.classList.add(...elementClassList, 'alert'); return Promise.resolve(); } if (showIcons && !showBackground) { context.classList = elementClassList.join(' '); } // The icons are displayed either with, or without, the background. return Templates.renderForPromise('block_accessreview/status', context) .then(({html, js}) => { Templates.appendNodeContents(element, html, js); if (showBackground) { element.classList.add(...elementClassList, 'alert'); } return; }) .catch(); }; /** * Applies the template to all sections and modules on the course page. * * @param {Number} courseId * @param {String} displayFormat * @param {Boolean} updatePreference * @returns {Promise} */ const showAccessMap = (courseId, displayFormat, updatePreference = false) => { // Get error data. return Promise.all(fetchReviewData(courseId, updatePreference)) .then(([sectionData, moduleData]) => { // Get total data. const {minViews, viewDelta} = getErrorTotals(sectionData, moduleData); sectionData.forEach(section => { const element = document.querySelector(`#section-${section.section} .summary`); if (!element) { return; } renderTemplate(element, section.numerrors, section.numchecks, displayFormat, minViews, viewDelta); }); moduleData.forEach(module => { const element = document.getElementById(`module-${module.cmid}`); if (!element) { return; } renderTemplate(element, module.numerrors, module.numchecks, displayFormat, minViews, viewDelta); }); // Change the icon display. document.querySelector('.icon-accessmap').classList.remove(...['fa-eye-slash']); document.querySelector('.icon-accessmap').classList.add(...['fa-eye']); return { sectionData, moduleData, }; }) .catch(displayError); }; /** * Hides or removes the templates from the HTML of the current page. * * @param {Boolean} updatePreference */ const hideAccessMap = (updatePreference = false) => { // Removes the added elements. document.querySelectorAll('.block_accessreview_view').forEach(node => node.remove()); const classList = [ 'block_accessreview', 'block_accessreview_success', 'block_accessreview_warning', 'block_accessreview_danger', 'block_accessreview_view', 'alert', ]; // Removes the added classes. document.querySelectorAll('.block_accessreview').forEach(node => node.classList.remove(...classList)); if (updatePreference) { setToggleStatePreference(false); } // Change the icon display. document.querySelector('.icon-accessmap').classList.remove(...['fa-eye']); document.querySelector('.icon-accessmap').classList.add(...['fa-eye-slash']); }; /** * Toggles the heatmap on/off. * * @param {Number} courseId * @param {String} displayFormat */ const toggleAccessMap = (courseId, displayFormat) => { toggleState = !toggleState; if (!toggleState) { hideAccessMap(true); } else { showAccessMap(courseId, displayFormat, true); } }; /** * Parses information on the errors, generating the min, max and totals. * * @param {Object[]} sectionData The error data for course sections. * @param {Object[]} moduleData The error data for course modules. * @returns {Object} An object representing the extra error information. */ const getErrorTotals = (sectionData, moduleData) => { const totals = { totalErrors: 0, totalUsers: 0, minViews: 0, maxViews: 0, viewDelta: 0, }; [].concat(sectionData, moduleData).forEach(item => { totals.totalErrors += item.numerrors; if (item.numerrors < totals.minViews) { totals.minViews = item.numerrors; } if (item.numerrors > totals.maxViews) { totals.maxViews = item.numerrors; } totals.totalUsers += item.numchecks; }); totals.viewDelta = totals.maxViews - totals.minViews + 1; return totals; }; const registerEventListeners = (courseId, displayFormat) => { document.addEventListener('click', e => { if (e.target.closest('#toggle-accessmap')) { e.preventDefault(); toggleAccessMap(courseId, displayFormat); } }); }; /** * Set the user preference for the toggle value. * * @param {Boolean} toggleState * @returns {Promise} */ const getTogglePreferenceParams = toggleState => { return { methodname: 'core_user_update_user_preferences', args: { preferences: [{ type: 'block_accessreviewtogglestate', value: toggleState, }], } }; }; const setToggleStatePreference = toggleState => fetchMany([getTogglePreferenceParams(toggleState)]); /** * Fetch the review data. * * @param {Number} courseid * @param {Boolean} updatePreference * @returns {Promise[]} */ const fetchReviewData = (courseid, updatePreference = false) => { const calls = [ { methodname: 'block_accessreview_get_section_data', args: {courseid} }, { methodname: 'block_accessreview_get_module_data', args: {courseid} }, ]; if (updatePreference) { calls.push(getTogglePreferenceParams(true)); } return fetchMany(calls); }; /** * Setting up the access review module. * @param {number} toggled A number represnting the state of the review toggle. * @param {string} displayFormat A string representing the display format for icons. * @param {number} courseId The course ID. */ export const init = (toggled, displayFormat, courseId) => { // Settings consts. toggleState = toggled == 1; if (toggleState) { showAccessMap(courseId, displayFormat); } registerEventListeners(courseId, displayFormat); }; PK'9\"{{$accessreview/amd/build/module.min.jsnu[define("block_accessreview/module",["exports","core/ajax","core/templates","core/notification"],(function(_exports,_ajax,Templates,_notification){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,Templates=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj} /** * Manager for the accessreview block. * * @module block_accessreview/module * @author Max Larkin * @copyright 2020 Brickfield Education Labs * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */(Templates);let toggleState=!0;const renderTemplate=(element,errorCount,checkCount,displayFormat,minViews,viewDelta)=>{const weight=parseInt((errorCount-minViews)/viewDelta*2),context={resultPassed:!errorCount,classList:"",passRate:{errorCount:errorCount,checkCount:checkCount,failureRate:Math.round(errorCount/checkCount*100)}};if(!element)return Promise.resolve();const elementClassList=["block_accessreview"];context.resultPassed?elementClassList.push("block_accessreview_success"):weight?elementClassList.push("block_accessreview_danger"):elementClassList.push("block_accessreview_warning");const showIcons="showicons"==displayFormat||"showboth"==displayFormat,showBackground="showbackground"==displayFormat||"showboth"==displayFormat;return showBackground&&!showIcons?(element.classList.add(...elementClassList,"alert"),Promise.resolve()):(showIcons&&!showBackground&&(context.classList=elementClassList.join(" ")),Templates.renderForPromise("block_accessreview/status",context).then((_ref=>{let{html:html,js:js}=_ref;Templates.appendNodeContents(element,html,js),showBackground&&element.classList.add(...elementClassList,"alert")})).catch())},showAccessMap=function(courseId,displayFormat){let updatePreference=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return Promise.all(fetchReviewData(courseId,updatePreference)).then((_ref2=>{let[sectionData,moduleData]=_ref2;const{minViews:minViews,viewDelta:viewDelta}=getErrorTotals(sectionData,moduleData);return sectionData.forEach((section=>{const element=document.querySelector("#section-".concat(section.section," .summary"));element&&renderTemplate(element,section.numerrors,section.numchecks,displayFormat,minViews,viewDelta)})),moduleData.forEach((module=>{const element=document.getElementById("module-".concat(module.cmid));element&&renderTemplate(element,module.numerrors,module.numchecks,displayFormat,minViews,viewDelta)})),document.querySelector(".icon-accessmap").classList.remove("fa-eye-slash"),document.querySelector(".icon-accessmap").classList.add("fa-eye"),{sectionData:sectionData,moduleData:moduleData}})).catch(_notification.exception)},toggleAccessMap=(courseId,displayFormat)=>{toggleState=!toggleState,toggleState?showAccessMap(courseId,displayFormat,!0):function(){let updatePreference=arguments.length>0&&void 0!==arguments[0]&&arguments[0];document.querySelectorAll(".block_accessreview_view").forEach((node=>node.remove()));const classList=["block_accessreview","block_accessreview_success","block_accessreview_warning","block_accessreview_danger","block_accessreview_view","alert"];document.querySelectorAll(".block_accessreview").forEach((node=>node.classList.remove(...classList))),updatePreference&&setToggleStatePreference(!1),document.querySelector(".icon-accessmap").classList.remove("fa-eye"),document.querySelector(".icon-accessmap").classList.add("fa-eye-slash")}(!0)},getErrorTotals=(sectionData,moduleData)=>{const totals={totalErrors:0,totalUsers:0,minViews:0,maxViews:0,viewDelta:0};return[].concat(sectionData,moduleData).forEach((item=>{totals.totalErrors+=item.numerrors,item.numerrorstotals.maxViews&&(totals.maxViews=item.numerrors),totals.totalUsers+=item.numchecks})),totals.viewDelta=totals.maxViews-totals.minViews+1,totals},getTogglePreferenceParams=toggleState=>({methodname:"core_user_update_user_preferences",args:{preferences:[{type:"block_accessreviewtogglestate",value:toggleState}]}}),setToggleStatePreference=toggleState=>(0,_ajax.call)([getTogglePreferenceParams(toggleState)]),fetchReviewData=function(courseid){let updatePreference=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const calls=[{methodname:"block_accessreview_get_section_data",args:{courseid:courseid}},{methodname:"block_accessreview_get_module_data",args:{courseid:courseid}}];return updatePreference&&calls.push(getTogglePreferenceParams(!0)),(0,_ajax.call)(calls)};_exports.init=(toggled,displayFormat,courseId)=>{toggleState=1==toggled,toggleState&&showAccessMap(courseId,displayFormat),((courseId,displayFormat)=>{document.addEventListener("click",(e=>{e.target.closest("#toggle-accessmap")&&(e.preventDefault(),toggleAccessMap(courseId,displayFormat))}))})(courseId,displayFormat)}})); //# sourceMappingURL=module.min.js.mapPK'9\nh-2-2(accessreview/amd/build/module.min.js.mapnu[{"version":3,"file":"module.min.js","sources":["../src/module.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Manager for the accessreview block.\n *\n * @module block_accessreview/module\n * @author Max Larkin \n * @copyright 2020 Brickfield Education Labs \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport * as Templates from 'core/templates';\nimport {exception as displayError} from 'core/notification';\n\n/**\n * The number of colours used to represent the heatmap. (Indexed on 0.)\n * @type {number}\n */\nconst numColours = 2;\n\n/**\n * The toggle state of the heatmap.\n * @type {boolean}\n */\nlet toggleState = true;\n\n/**\n * Renders the HTML template onto a particular HTML element.\n * @param {HTMLElement} element The element to attach the HTML to.\n * @param {number} errorCount The number of errors on this module/section.\n * @param {number} checkCount The number of checks triggered on this module/section.\n * @param {String} displayFormat\n * @param {Number} minViews\n * @param {Number} viewDelta\n * @returns {Promise}\n */\nconst renderTemplate = (element, errorCount, checkCount, displayFormat, minViews, viewDelta) => {\n // Calculate a weight?\n const weight = parseInt((errorCount - minViews) / viewDelta * numColours);\n\n const context = {\n resultPassed: !errorCount,\n classList: '',\n passRate: {\n errorCount,\n checkCount,\n failureRate: Math.round(errorCount / checkCount * 100),\n },\n };\n\n if (!element) {\n return Promise.resolve();\n }\n\n const elementClassList = ['block_accessreview'];\n if (context.resultPassed) {\n elementClassList.push('block_accessreview_success');\n } else if (weight) {\n elementClassList.push('block_accessreview_danger');\n } else {\n elementClassList.push('block_accessreview_warning');\n }\n\n const showIcons = (displayFormat == 'showicons') || (displayFormat == 'showboth');\n const showBackground = (displayFormat == 'showbackground') || (displayFormat == 'showboth');\n\n if (showBackground && !showIcons) {\n // Only the background is displayed.\n // No need to display the template.\n // Note: The case where both the background and icons are shown is handled later to avoid jankiness.\n element.classList.add(...elementClassList, 'alert');\n\n return Promise.resolve();\n }\n\n if (showIcons && !showBackground) {\n context.classList = elementClassList.join(' ');\n }\n\n // The icons are displayed either with, or without, the background.\n return Templates.renderForPromise('block_accessreview/status', context)\n .then(({html, js}) => {\n Templates.appendNodeContents(element, html, js);\n\n if (showBackground) {\n element.classList.add(...elementClassList, 'alert');\n }\n\n return;\n })\n .catch();\n};\n\n/**\n * Applies the template to all sections and modules on the course page.\n *\n * @param {Number} courseId\n * @param {String} displayFormat\n * @param {Boolean} updatePreference\n * @returns {Promise}\n */\nconst showAccessMap = (courseId, displayFormat, updatePreference = false) => {\n // Get error data.\n return Promise.all(fetchReviewData(courseId, updatePreference))\n .then(([sectionData, moduleData]) => {\n // Get total data.\n const {minViews, viewDelta} = getErrorTotals(sectionData, moduleData);\n\n sectionData.forEach(section => {\n const element = document.querySelector(`#section-${section.section} .summary`);\n if (!element) {\n return;\n }\n\n renderTemplate(element, section.numerrors, section.numchecks, displayFormat, minViews, viewDelta);\n });\n\n moduleData.forEach(module => {\n const element = document.getElementById(`module-${module.cmid}`);\n if (!element) {\n return;\n }\n\n renderTemplate(element, module.numerrors, module.numchecks, displayFormat, minViews, viewDelta);\n });\n\n // Change the icon display.\n document.querySelector('.icon-accessmap').classList.remove(...['fa-eye-slash']);\n document.querySelector('.icon-accessmap').classList.add(...['fa-eye']);\n\n return {\n sectionData,\n moduleData,\n };\n })\n .catch(displayError);\n};\n\n\n/**\n * Hides or removes the templates from the HTML of the current page.\n *\n * @param {Boolean} updatePreference\n */\nconst hideAccessMap = (updatePreference = false) => {\n // Removes the added elements.\n document.querySelectorAll('.block_accessreview_view').forEach(node => node.remove());\n\n const classList = [\n 'block_accessreview',\n 'block_accessreview_success',\n 'block_accessreview_warning',\n 'block_accessreview_danger',\n 'block_accessreview_view',\n 'alert',\n ];\n\n // Removes the added classes.\n document.querySelectorAll('.block_accessreview').forEach(node => node.classList.remove(...classList));\n\n if (updatePreference) {\n setToggleStatePreference(false);\n }\n\n // Change the icon display.\n document.querySelector('.icon-accessmap').classList.remove(...['fa-eye']);\n document.querySelector('.icon-accessmap').classList.add(...['fa-eye-slash']);\n};\n\n\n/**\n * Toggles the heatmap on/off.\n *\n * @param {Number} courseId\n * @param {String} displayFormat\n */\nconst toggleAccessMap = (courseId, displayFormat) => {\n toggleState = !toggleState;\n if (!toggleState) {\n hideAccessMap(true);\n } else {\n showAccessMap(courseId, displayFormat, true);\n }\n};\n\n/**\n * Parses information on the errors, generating the min, max and totals.\n *\n * @param {Object[]} sectionData The error data for course sections.\n * @param {Object[]} moduleData The error data for course modules.\n * @returns {Object} An object representing the extra error information.\n */\nconst getErrorTotals = (sectionData, moduleData) => {\n const totals = {\n totalErrors: 0,\n totalUsers: 0,\n minViews: 0,\n maxViews: 0,\n viewDelta: 0,\n };\n\n [].concat(sectionData, moduleData).forEach(item => {\n totals.totalErrors += item.numerrors;\n if (item.numerrors < totals.minViews) {\n totals.minViews = item.numerrors;\n }\n\n if (item.numerrors > totals.maxViews) {\n totals.maxViews = item.numerrors;\n }\n totals.totalUsers += item.numchecks;\n });\n\n totals.viewDelta = totals.maxViews - totals.minViews + 1;\n\n return totals;\n};\n\nconst registerEventListeners = (courseId, displayFormat) => {\n document.addEventListener('click', e => {\n if (e.target.closest('#toggle-accessmap')) {\n e.preventDefault();\n toggleAccessMap(courseId, displayFormat);\n }\n });\n};\n\n/**\n * Set the user preference for the toggle value.\n *\n * @param {Boolean} toggleState\n * @returns {Promise}\n */\nconst getTogglePreferenceParams = toggleState => {\n return {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [{\n type: 'block_accessreviewtogglestate',\n value: toggleState,\n }],\n }\n };\n};\n\nconst setToggleStatePreference = toggleState => fetchMany([getTogglePreferenceParams(toggleState)]);\n\n/**\n * Fetch the review data.\n *\n * @param {Number} courseid\n * @param {Boolean} updatePreference\n * @returns {Promise[]}\n */\nconst fetchReviewData = (courseid, updatePreference = false) => {\n const calls = [\n {\n methodname: 'block_accessreview_get_section_data',\n args: {courseid}\n },\n {\n methodname: 'block_accessreview_get_module_data',\n args: {courseid}\n },\n ];\n\n if (updatePreference) {\n calls.push(getTogglePreferenceParams(true));\n }\n\n return fetchMany(calls);\n};\n\n/**\n * Setting up the access review module.\n * @param {number} toggled A number represnting the state of the review toggle.\n * @param {string} displayFormat A string representing the display format for icons.\n * @param {number} courseId The course ID.\n */\nexport const init = (toggled, displayFormat, courseId) => {\n // Settings consts.\n toggleState = toggled == 1;\n\n if (toggleState) {\n showAccessMap(courseId, displayFormat);\n }\n\n registerEventListeners(courseId, displayFormat);\n};\n"],"names":["toggleState","renderTemplate","element","errorCount","checkCount","displayFormat","minViews","viewDelta","weight","parseInt","context","resultPassed","classList","passRate","failureRate","Math","round","Promise","resolve","elementClassList","push","showIcons","showBackground","add","join","Templates","renderForPromise","then","_ref","html","js","appendNodeContents","catch","showAccessMap","courseId","updatePreference","all","fetchReviewData","_ref2","sectionData","moduleData","getErrorTotals","forEach","section","document","querySelector","numerrors","numchecks","module","getElementById","cmid","remove","displayError","toggleAccessMap","querySelectorAll","node","setToggleStatePreference","hideAccessMap","totals","totalErrors","totalUsers","maxViews","concat","item","getTogglePreferenceParams","methodname","args","preferences","type","value","courseid","calls","toggled","addEventListener","e","target","closest","preventDefault","registerEventListeners"],"mappings":";;;;;;;;qBAsCIA,aAAc,QAYZC,eAAiB,CAACC,QAASC,WAAYC,WAAYC,cAAeC,SAAUC,mBAExEC,OAASC,UAAUN,WAAaG,UAAYC,UApBnC,GAsBTG,QAAU,CACZC,cAAeR,WACfS,UAAW,GACXC,SAAU,CACNV,WAAAA,WACAC,WAAAA,WACAU,YAAaC,KAAKC,MAAMb,WAAaC,WAAa,WAIrDF,eACMe,QAAQC,gBAGbC,iBAAmB,CAAC,sBACtBT,QAAQC,aACRQ,iBAAiBC,KAAK,8BACfZ,OACPW,iBAAiBC,KAAK,6BAEtBD,iBAAiBC,KAAK,oCAGpBC,UAA8B,aAAjBhB,eAAmD,YAAjBA,cAC/CiB,eAAmC,kBAAjBjB,eAAwD,YAAjBA,qBAE3DiB,iBAAmBD,WAInBnB,QAAQU,UAAUW,OAAOJ,iBAAkB,SAEpCF,QAAQC,YAGfG,YAAcC,iBACdZ,QAAQE,UAAYO,iBAAiBK,KAAK,MAIvCC,UAAUC,iBAAiB,4BAA6BhB,SAC9DiB,MAAKC,WAACC,KAACA,KAADC,GAAOA,SACVL,UAAUM,mBAAmB7B,QAAS2B,KAAMC,IAExCR,gBACApB,QAAQU,UAAUW,OAAOJ,iBAAkB,YAKlDa,UAWCC,cAAgB,SAACC,SAAU7B,mBAAe8B,gFAErClB,QAAQmB,IAAIC,gBAAgBH,SAAUC,mBAC5CR,MAAKW,YAAEC,YAAaC,wBAEXlC,SAACA,SAADC,UAAWA,WAAakC,eAAeF,YAAaC,mBAE1DD,YAAYG,SAAQC,gBACVzC,QAAU0C,SAASC,iCAA0BF,QAAQA,sBACtDzC,SAILD,eAAeC,QAASyC,QAAQG,UAAWH,QAAQI,UAAW1C,cAAeC,SAAUC,cAG3FiC,WAAWE,SAAQM,eACT9C,QAAU0C,SAASK,gCAAyBD,OAAOE,OACpDhD,SAILD,eAAeC,QAAS8C,OAAOF,UAAWE,OAAOD,UAAW1C,cAAeC,SAAUC,cAIzFqC,SAASC,cAAc,mBAAmBjC,UAAUuC,OAAW,gBAC/DP,SAASC,cAAc,mBAAmBjC,UAAUW,IAAQ,UAErD,CACHgB,YAAAA,YACAC,WAAAA,eAGPR,MAAMoB,0BAyCLC,gBAAkB,CAACnB,SAAU7B,iBAC/BL,aAAeA,YACVA,YAGDiC,cAAcC,SAAU7B,eAAe,GArCzB,eAAC8B,yEAEnBS,SAASU,iBAAiB,4BAA4BZ,SAAQa,MAAQA,KAAKJ,iBAErEvC,UAAY,CACd,qBACA,6BACA,6BACA,4BACA,0BACA,SAIJgC,SAASU,iBAAiB,uBAAuBZ,SAAQa,MAAQA,KAAK3C,UAAUuC,UAAUvC,aAEtFuB,kBACAqB,0BAAyB,GAI7BZ,SAASC,cAAc,mBAAmBjC,UAAUuC,OAAW,UAC/DP,SAASC,cAAc,mBAAmBjC,UAAUW,IAAQ,gBAaxDkC,EAAc,IAahBhB,eAAiB,CAACF,YAAaC,oBAC3BkB,OAAS,CACXC,YAAa,EACbC,WAAY,EACZtD,SAAU,EACVuD,SAAU,EACVtD,UAAW,YAGZuD,OAAOvB,YAAaC,YAAYE,SAAQqB,OACvCL,OAAOC,aAAeI,KAAKjB,UACvBiB,KAAKjB,UAAYY,OAAOpD,WACxBoD,OAAOpD,SAAWyD,KAAKjB,WAGvBiB,KAAKjB,UAAYY,OAAOG,WACxBH,OAAOG,SAAWE,KAAKjB,WAE3BY,OAAOE,YAAcG,KAAKhB,aAG9BW,OAAOnD,UAAYmD,OAAOG,SAAWH,OAAOpD,SAAW,EAEhDoD,QAkBLM,0BAA4BhE,cACvB,CACHiE,WAAY,oCACZC,KAAM,CACFC,YAAa,CAAC,CACVC,KAAM,gCACNC,MAAOrE,iBAMjBwD,yBAA2BxD,cAAe,cAAU,CAACgE,0BAA0BhE,eAS/EqC,gBAAkB,SAACiC,cAAUnC,+EACzBoC,MAAQ,CACV,CACIN,WAAY,sCACZC,KAAM,CAACI,SAAAA,WAEX,CACIL,WAAY,qCACZC,KAAM,CAACI,SAAAA,mBAIXnC,kBACAoC,MAAMnD,KAAK4C,2BAA0B,KAGlC,cAAUO,sBASD,CAACC,QAASnE,cAAe6B,YAEzClC,YAAyB,GAAXwE,QAEVxE,aACAiC,cAAcC,SAAU7B,eAlED,EAAC6B,SAAU7B,iBACtCuC,SAAS6B,iBAAiB,SAASC,IAC3BA,EAAEC,OAAOC,QAAQ,uBACjBF,EAAEG,iBACFxB,gBAAgBnB,SAAU7B,oBAiElCyE,CAAuB5C,SAAU7B"}PK'9\Ȏ/accessreview/tests/accessreview_results.featurenu[@block @block_accessreview @javascript Feature: Block accessreview results In order to overview accessibility information on my course As a manager I can add the accessreview block in a course after running the scheduled task Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "activities" exist: | activity | name | intro | course | idnumber | | label | Label one | Bold text is bold. | C1 | id001 | | label | Label two | Click here | C1 | id002 | And I run the scheduled task "\tool_brickfield\task\process_analysis_requests" And I run the scheduled task "\tool_brickfield\task\bulk_process_courses" And I run the scheduled task "\tool_brickfield\task\bulk_process_caches" Scenario: View accessreview block results on a course Given I log in as "admin" And I navigate to "Plugins > Admin tools > Accessibility > Brickfield registration" in site administration And I set the field "id_key" to "123456789012345678901234567890ab" And I set the field "id_hash" to "ab123456789012345678901234567890" And I press "Activate" Then I should see "Your accessibility toolkit is still being validated." And I am on "Course 1" course homepage with editing mode on When I add the "Accessibility Review" block Then I should see "Accessibility Review" And I press "Submit for analysis" Then I should see "This course has been scheduled for analysis." And I run the scheduled task "\tool_brickfield\task\process_analysis_requests" And I run the scheduled task "\tool_brickfield\task\bulk_process_courses" And I run the scheduled task "\tool_brickfield\task\bulk_process_caches" And I reload the page And I should see "Image" in the "Accessibility Review" "block" And I should see "Layout" in the "Accessibility Review" "block" And I should see "Link" in the "Accessibility Review" "block" And I should see "Media" in the "Accessibility Review" "block" And I should see "Table" in the "Accessibility Review" "block" And I should see "Text" in the "Accessibility Review" "block" # We created one link error above. And I should see "1" in the "Link" "table_row" # We created one text issue, and the standard Behat course generator creates another (too much content). And I should see "2" in the "Text" "table_row" And "View Brickfield Accessibility Report" "icon" should exist in the "Accessibility Review" "block" And "Toggle Brickfield Accessibility Heatmap" "icon" should exist in the "Accessibility Review" "block" And "Download Brickfield Accessibility Report" "icon" should exist in the "Accessibility Review" "block" Scenario: Toggle highlighting on/off Given I log in as "admin" Given the following "user preferences" exist: | user | preference | value | | admin | block_accessreviewtogglestate | 0 | And I navigate to "Plugins > Admin tools > Accessibility > Brickfield registration" in site administration And I set the field "id_key" to "123456789012345678901234567890ab" And I set the field "id_hash" to "ab123456789012345678901234567890" And I press "Activate" And I am on "Course 1" course homepage with editing mode on When I add the "Accessibility Review" block And I press "Submit for analysis" And I run the scheduled task "\tool_brickfield\task\process_analysis_requests" And I run the scheduled task "\tool_brickfield\task\bulk_process_courses" And I run the scheduled task "\tool_brickfield\task\bulk_process_caches" And I reload the page And I click on "Toggle Brickfield Accessibility Heatmap" "icon" And ".block_accessreview_view" "css_element" should be visible And I click on "Toggle Brickfield Accessibility Heatmap" "icon" And ".block_accessreview_view" "css_element" should not be visible PK'9\mT0accessreview/tests/accessibility_review_test.phpnu[. namespace block_accessreview; use ReflectionClass; use advanced_testcase; use block_accessreview; use context_course; /** * PHPUnit block_accessibility_review tests * * @package block_accessreview * @copyright 2020 onward: Learning Technology Services, www.lts.ie * @author Jay Churchward (jay.churchward@poetopensource.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_accessreview */ final class accessibility_review_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_accessreview.php'); parent::setUpBeforeClass(); } public function test_get_toggle_link(): void { $rc = new ReflectionClass(block_accessreview::class); $rm = $rc->getMethod('get_toggle_link'); $block = new block_accessreview(); $output = $rm->invoke($block); $this->assertNotEmpty($output); } public function test_get_download_link(): void { $this->resetAfterTest(); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); // Enrol users in the course. $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'teacher'); $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student'); $rc = new ReflectionClass(block_accessreview::class); $rm = $rc->getMethod('get_download_link'); $block = new block_accessreview(); $this->setUser($user1); $result = $rm->invoke($block, context_course::instance($course->id)); $this->assertNotEmpty($result); $this->setUser($user2); $result = $rm->invoke($block, context_course::instance($course->id)); $this->assertEmpty($result); } public function test_get_report_link(): void { $this->resetAfterTest(); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); // Enrol users in the course. $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'teacher'); $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student'); $rc = new ReflectionClass(block_accessreview::class); $rm = $rc->getMethod('get_report_link'); $block = new block_accessreview(); $this->setUser($user1); $result = $rm->invoke($block, context_course::instance($course->id)); $this->assertNotEmpty($result); $this->setUser($user2); $result = $rm->invoke($block, context_course::instance($course->id)); $this->assertEmpty($result); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_accessreview(); // If the accessibility tools is enabled, the method should return true. set_config('enableaccessibilitytools', true); $this->assertTrue($block->can_block_be_added($page)); // However, if the accessibility tools is disabled, the method should return false. set_config('enableaccessibilitytools', false); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\M7--5accessreview/tests/behat/accessreview_results.featurenu[@block @block_accessreview @javascript Feature: Block accessreview results In order to overview accessibility information on my course As a manager I can add the accessreview block in a course after running the scheduled task Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "activities" exist: | activity | name | intro | course | idnumber | | label | Label one | Bold text is bold. | C1 | id001 | | label | Label two | Click here | C1 | id002 | And the following config values are set as admin: | analysistype | 1 | tool_brickfield | Scenario: View accessreview block results on a course Given I log in as "admin" And I navigate to "Plugins > Admin tools > Accessibility > Brickfield registration" in site administration And I set the field "id_key" to "123456789012345678901234567890ab" And I set the field "id_hash" to "ab123456789012345678901234567890" And I press "Activate" Then I should see "Your accessibility toolkit is functional while being validated." And I am on "Course 1" course homepage with editing mode on When I add the "Accessibility review" block Then I should see "Accessibility review" And I press "Submit for analysis" Then I should see "This course has been scheduled for analysis." And I run the scheduled task "\tool_brickfield\task\process_analysis_requests" And I run the scheduled task "\tool_brickfield\task\bulk_process_courses" And I run the scheduled task "\tool_brickfield\task\bulk_process_caches" And I reload the page And I should see "Image" in the "Accessibility review" "block" And I should see "Layout" in the "Accessibility review" "block" And I should see "Link" in the "Accessibility review" "block" And I should see "Media" in the "Accessibility review" "block" And I should see "Table" in the "Accessibility review" "block" And I should see "Text" in the "Accessibility review" "block" # We created one link error above. And I should see "1" in the "Link" "table_row" # We created one text issue, and the standard Behat course generator creates another (too much content). And I should see "2" in the "Text" "table_row" And "View accessibility toolkit" "icon" should exist in the "Accessibility review" "block" And "Toggle accessibility heatmap" "icon" should exist in the "Accessibility review" "block" And "Download accessibility summary report" "icon" should exist in the "Accessibility review" "block" Scenario: Toggle highlighting on/off Given I log in as "admin" Given the following "user preferences" exist: | user | preference | value | | admin | block_accessreviewtogglestate | 0 | And I navigate to "Plugins > Admin tools > Accessibility > Brickfield registration" in site administration And I set the field "id_key" to "123456789012345678901234567890ab" And I set the field "id_hash" to "ab123456789012345678901234567890" And I press "Activate" And I am on "Course 1" course homepage with editing mode on When I add the "Accessibility review" block And I press "Submit for analysis" And I run the scheduled task "\tool_brickfield\task\process_analysis_requests" And I run the scheduled task "\tool_brickfield\task\bulk_process_courses" And I run the scheduled task "\tool_brickfield\task\bulk_process_caches" And I reload the page And I click on "Toggle accessibility heatmap" "icon" And ".block_accessreview_view" "css_element" should be visible And I click on "Toggle accessibility heatmap" "icon" And ".block_accessreview_view" "css_element" should not be visible PK'9\n-accessreview/tests/behat/accessreview.featurenu[@block @block_accessreview @javascript Feature: Block accessreview In order to overview accessibility information on my course As a manager I can add the accessreview block in a course Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following config values are set as admin: | analysistype | 1 | tool_brickfield | Scenario: View accessreview block on a course Given I log in as "admin" And I am on "Course 1" course homepage with editing mode on When I add the "Accessibility review" block Then I should see "Accessibility review" And I should see "Your accessibility toolkit needs to be registered." Scenario: Hide/show accessreview view Given I log in as "admin" And I am on "Course 1" course homepage with editing mode on When I add the "Accessibility review" block Then I should see "Accessibility review" And I should see "Your accessibility toolkit needs to be registered." And I click on "Actions menu" "menuitem" in the "Accessibility review" "block" And I follow "Hide Accessibility review block" And I should not see "Your accessibility toolkit needs to be registered." And I click on "Actions menu" "menuitem" in the "Accessibility review" "block" And I follow "Show Accessibility review block" And I should see "Your accessibility toolkit needs to be registered." PK'9\JC)OO'accessreview/tests/accessreview.featurenu[@block @block_accessreview @javascript Feature: Block accessreview In order to overview accessibility information on my course As a manager I can add the accessreview block in a course Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | Scenario: View accessreview block on a course Given I log in as "admin" And I am on "Course 1" course homepage with editing mode on When I add the "Accessibility Review" block Then I should see "Accessibility Review" And I should see "Your accessibility toolkit needs to be registered." Scenario: Hide/show accessreview view Given I log in as "admin" And I am on "Course 1" course homepage with editing mode on When I add the "Accessibility Review" block Then I should see "Accessibility Review" And I should see "Your accessibility toolkit needs to be registered." And I click on "Actions menu" "menuitem" in the "Accessibility Review" "block" And I follow "Hide Accessibility Review block" And I should not see "Your accessibility toolkit needs to be registered." And I click on "Actions menu" "menuitem" in the "Accessibility Review" "block" And I follow "Show Accessibility Review block" And I should see "Your accessibility toolkit needs to be registered." PK'9\Ăraccessreview/settings.phpnu[. /** * accessreview block settings * * @package block_accessreview * @copyright 2019 Karen Holland LTS.ie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); use tool_brickfield\local\tool\tool as base_tool; if ($ADMIN->fulltree) { // Control background display and icons. $options = [ 'showboth' => get_string('showboth', 'block_accessreview'), 'showbackground' => get_string('showbackground', 'block_accessreview'), 'showicons' => get_string('showicons', 'block_accessreview'), ]; $settings->add(new admin_setting_configselect('block_accessreview/whattoshow', get_string('whattoshow', 'block_accessreview'), '', 'showboth', $options) ); // Control display format for errors. $erroroptions = [ 'showint' => get_string('showint', 'block_accessreview'), 'showpercent' => get_string('showpercent', 'block_accessreview'), 'showicon' => get_string('showicon', 'block_accessreview'), ]; $settings->add(new admin_setting_configselect('block_accessreview/errordisplay', get_string('errordisplay', 'block_accessreview'), '', 'showint', $erroroptions) ); // Tool page to display by default. $options = base_tool::get_tool_names(); $settings->add(new admin_setting_configselect( 'block_accessreview/toolpage', get_string('toolpage', 'block_accessreview'), '', key($options), $options )); } PK'9\m=TTstarredcourses/db/services.phpnu[. /** * File description. * * @package block_starredcourses * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $functions = array( 'block_starredcourses_get_starred_courses' => array( 'classpath' => 'block/starredcourses/classes/external.php', 'classname' => 'block_starredcourses_external', 'methodname' => 'get_starred_courses', 'description' => 'Get users starred courses.', 'type' => 'read', 'ajax' => true, 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), ); PK'9\Dmstarredcourses/db/access.phpnu[. /** * Starred courses block capabilities. * * @package block_starredcourses * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/starredcourses:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ) ); PK'9\iGa00/starredcourses/lang/en/block_starredcourses.phpnu[. /** * Starred courses block language strings. * * @package block_starredcourses * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['displaycategories'] = 'Display categories'; $string['displaycategories_help'] = 'Display the course category on the starred courses block items.'; $string['pluginname'] = 'Starred courses'; $string['privacy:metadata'] = 'The starred courses block does not store any personal data.'; $string['starredcourses:myaddinstance'] = 'Add a new starred courses block to Dashboard'; $string['nocourses'] = 'No starred courses';PK'9\aestarredcourses/pix/courses.svgnu[ PK'9\Jstarredcourses/version.phpnu[. /** * Starred courses block version details. * * @package block_starredcourses * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; $plugin->version = 2025041400; $plugin->requires = 2025040800; $plugin->component = 'block_starredcourses';PK'9\c#starredcourses/classes/external.phpnu[. /** * Starred courses block external API * * @package block_starredcourses * @category external * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot . '/course/lib.php'); require_once($CFG->dirroot . '/course/externallib.php'); use core_course\external\course_summary_exporter; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_value; /** * Starred courses block external functions. * * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_starredcourses_external extends core_course_external { /** * Returns description of method parameters * * @return external_function_parameters * @since Moodle 3.6 */ public static function get_starred_courses_parameters() { return new external_function_parameters([ 'limit' => new external_value(PARAM_INT, 'Limit', VALUE_DEFAULT, 0), 'offset' => new external_value(PARAM_INT, 'Offset', VALUE_DEFAULT, 0) ]); } /** * Get users starred courses. * * @param int $limit Limit * @param int $offset Offset * * @return array list of courses and warnings */ public static function get_starred_courses($limit, $offset) { global $USER, $PAGE; $params = self::validate_parameters(self::get_starred_courses_parameters(), [ 'limit' => $limit, 'offset' => $offset ]); $limit = $params['limit']; $offset = $params['offset']; $usercontext = context_user::instance($USER->id); self::validate_context($usercontext); $PAGE->set_context($usercontext); $renderer = $PAGE->get_renderer('core'); // Get the user favourites service, scoped to a single user (their favourites only). $userservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); // Get the favourites, by type, for the user. $favourites = $userservice->find_favourites_by_type('core_course', 'courses', $offset, $limit); $favouritecourseids = []; if ($favourites) { $favouritecourseids = array_map( function($favourite) { return $favourite->itemid; }, $favourites); } // Get all courses that the current user is enroled in, restricted down to favourites. $filteredcourses = []; if ($favouritecourseids) { $courses = course_get_enrolled_courses_for_logged_in_user(0, 0, null, null, COURSE_DB_QUERY_LIMIT, $favouritecourseids); list($filteredcourses, $processedcount) = course_filter_courses_by_favourites( $courses, $favouritecourseids, 0 ); } // Grab the course ids. $filteredcourseids = array_column($filteredcourses, 'id'); // Filter out any favourites that are not in the list of enroled courses. $filteredfavourites = array_filter($favourites, function($favourite) use ($filteredcourseids) { return in_array($favourite->itemid, $filteredcourseids); }); // Sort the favourites getting last added first. usort($filteredfavourites, function($a, $b) { if ($a->timemodified == $b->timemodified) return 0; return ($a->timemodified > $b->timemodified) ? -1 : 1; }); $formattedcourses = array(); foreach ($filteredfavourites as $favourite) { $course = get_course($favourite->itemid); $context = \context_course::instance($favourite->itemid); $canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $context); if ($course->visible || $canviewhiddencourses) { $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => true]); $formattedcourse = $exporter->export($renderer); $formattedcourses[] = $formattedcourse; } } return $formattedcourses; } /** * Returns description of method result value * * @return \core_external\external_description * @since Moodle 3.6 */ public static function get_starred_courses_returns() { return new external_multiple_structure(course_summary_exporter::get_read_structure()); } } PK'9\U9;+starredcourses/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_starredcourses. * * @package block_starredcourses * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_starredcourses\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_starredcourses. * * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\<3[&starredcourses/classes/output/main.phpnu[. /** * Class containing data for starred courses block. * * @package block_starredcourses * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_starredcourses\output; defined('MOODLE_INTERNAL') || die(); use renderable; use renderer_base; use templatable; use core_course\external\course_summary_exporter; require_once($CFG->dirroot . '/course/lib.php'); require_once($CFG->libdir . '/completionlib.php'); /** * Class containing data for starred courses block. * * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class main implements renderable, templatable { /** * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output * @return array */ public function export_for_template(renderer_base $output) { global $USER; $nocoursesurl = $output->image_url('courses', 'block_starredcourses')->out(); $config = get_config('block_starredcourses'); return [ 'userid' => $USER->id, 'nocoursesimg' => $nocoursesurl, 'displaycategories' => !empty($config->displaycategories) ]; } } PK'9\<M%%*starredcourses/classes/output/renderer.phpnu[. /** * Starred courses block renderer. * * @package block_starredcourses * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_starredcourses\output; defined('MOODLE_INTERNAL') || die; use plugin_renderer_base; /** * Starred courses block renderer. * * @package block_starredcourses * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends plugin_renderer_base { /** * Return the main content for the block. * * @param main $main The main renderable * @return string HTML string */ public function render_main(main $main) { return $this->render_from_template('block_starredcourses/main', $main->export_for_template($this)); } } PK'9\2&starredcourses/templates/main.mustachenu[{{! 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 . }} {{! @template block_starredcourses/main This template renders the main content area for the starred courses block. Example context (json): {} }}
          {{> block_starredcourses/view }}
          {{#js}} require(['block_starredcourses/main'], function(Main) { Main.init('#block-starredcourses-{{uniqid}}'); }); {{/js}} PK'9\w`8>[[,starredcourses/templates/no-courses.mustachenu[{{! 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 . }} {{< core_course/no-courses}} {{$nocoursestring}} {{#str}} nocourses, block_starredcourses {{/str}} {{/nocoursestring}} {{/ core_course/no-courses}} PK'9\l^^&starredcourses/templates/view.mustachenu[{{! 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 . }} {{! @template block_starredcourses/view This template renders the view for the starred courses block. Example context (json): { "nocoursesimg": "https://moodlesite/theme/image.php/boost/block_starredcourses/1535727318/courses" } }}
          {{> core_course/placeholder-course }}
          {{> core_course/placeholder-course }}
          {{> core_course/placeholder-course }}
          PK'9\INdstarredcourses/upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This file describes API changes in the starredcourses block code. === 3.8 === * New admin setting block_starredcourses/displaycategories which enables the starred courses block to display the course category. === 3.7 === * The 'block/starredcourses:addinstance' capability has been removed. It has never been used in code.PK'9\$starredcourses/amd/src/repository.jsnu[// 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 . /** * A javascript module to retrieve user's starred courses. * * @module block_starredcourses/repository * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) { /** * Retrieve a list of starred courses. * * Valid args are: * int limit number of records to retrieve * int offset the offset of records to retrieve * * @method getStarredCourses * @param {object} args The request arguments * @return {promise} Resolved with an array of courses */ var getStarredCourses = function(args) { var request = { methodname: 'block_starredcourses_get_starred_courses', args: args }; var promise = Ajax.call([request])[0]; promise.fail(Notification.exception); return promise; }; return { getStarredCourses: getStarredCourses }; }); PK'9\}֐starredcourses/amd/src/main.jsnu[// 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 . /** * Javascript to initialise the starred courses block. * * @module block_starredcourses/main * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'core/notification', 'block_starredcourses/repository', 'core/pubsub', 'core/templates', 'core_course/events' ], function( $, Notification, Repository, PubSub, Templates, CourseEvents ) { var SELECTORS = { BLOCK_CONTAINER: '[data-region="starred-courses"]', STARRED_COURSES_REGION_VIEW: '[data-region="starred-courses-view"]', STARRED_COURSES_REGION: '[data-region="starred-courses-view-content"]' }; /** * Render the starred courses. * * @method renderCourses * @param {object} root The root element for the starred view. * @param {array} courses containing array of returned courses. * @returns {promise} Resolved with HTML and JS strings */ var renderCourses = function(root, courses) { if (courses.length > 0) { return Templates.render('core_course/view-cards', { courses: courses }); } else { var nocoursesimg = root.find(SELECTORS.STARRED_COURSES_REGION_VIEW).attr('data-nocoursesimg'); return Templates.render('block_starredcourses/no-courses', { nocoursesimg: nocoursesimg }); } }; /** * Fetch user's starred courses and reload the content of the block. * * @param {object} root The root element for the starred view. * @returns {promise} The updated content for the block. */ var reloadContent = function(root) { var content = root.find(SELECTORS.STARRED_COURSES_REGION); var args = { limit: 0, offset: 0, }; return Repository.getStarredCourses(args) .then(function(courses) { // Whether the course category should be displayed in the course item. var showcoursecategory = $(SELECTORS.BLOCK_CONTAINER).data('displaycoursecategory'); courses = courses.map(function(course) { course.showcoursecategory = showcoursecategory; return course; }); return renderCourses(root, courses); }).then(function(html, js) { return Templates.replaceNodeContents(content, html, js); }).catch(Notification.exception); }; /** * Register event listeners for the block. * * @param {object} root The calendar root element */ var registerEventListeners = function(root) { PubSub.subscribe(CourseEvents.favourited, function() { reloadContent(root); }); PubSub.subscribe(CourseEvents.unfavorited, function() { reloadContent(root); }); }; /** * Initialise all of the modules for the starred courses block. * * @param {object} root The root element for the block. */ var init = function(root) { root = $(root); registerEventListeners(root); reloadContent(root); }; return { init: init }; }); PK'9\rk[[*starredcourses/amd/build/repository.min.jsnu[/** * A javascript module to retrieve user's starred courses. * * @module block_starredcourses/repository * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_starredcourses/repository",["jquery","core/ajax","core/notification"],(function($,Ajax,Notification){return{getStarredCourses:function(args){var request={methodname:"block_starredcourses_get_starred_courses",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise}}})); //# sourceMappingURL=repository.min.js.mapPK'9\Hś.starredcourses/amd/build/repository.min.js.mapnu[{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to retrieve user's starred courses.\n *\n * @module block_starredcourses/repository\n * @copyright 2018 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) {\n\n /**\n * Retrieve a list of starred courses.\n *\n * Valid args are:\n * int limit number of records to retrieve\n * int offset the offset of records to retrieve\n *\n * @method getStarredCourses\n * @param {object} args The request arguments\n * @return {promise} Resolved with an array of courses\n */\n var getStarredCourses = function(args) {\n\n var request = {\n methodname: 'block_starredcourses_get_starred_courses',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n return {\n getStarredCourses: getStarredCourses\n };\n});\n"],"names":["define","$","Ajax","Notification","getStarredCourses","args","request","methodname","promise","call","fail","exception"],"mappings":";;;;;;;AAsBAA,yCAAO,CAAC,SAAU,YAAa,sBAAsB,SAASC,EAAGC,KAAMC,oBA2B5D,CACHC,kBAfoB,SAASC,UAEzBC,QAAU,CACVC,WAAY,2CACZF,KAAMA,MAGNG,QAAUN,KAAKO,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKP,aAAaQ,WAEnBH"}PK'9\> (starredcourses/amd/build/main.min.js.mapnu[{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to initialise the starred courses block.\n *\n * @module block_starredcourses/main\n * @copyright 2018 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n[\n 'jquery',\n 'core/notification',\n 'block_starredcourses/repository',\n 'core/pubsub',\n 'core/templates',\n 'core_course/events'\n],\nfunction(\n $,\n Notification,\n Repository,\n PubSub,\n Templates,\n CourseEvents\n) {\n\n var SELECTORS = {\n BLOCK_CONTAINER: '[data-region=\"starred-courses\"]',\n STARRED_COURSES_REGION_VIEW: '[data-region=\"starred-courses-view\"]',\n STARRED_COURSES_REGION: '[data-region=\"starred-courses-view-content\"]'\n };\n\n /**\n * Render the starred courses.\n *\n * @method renderCourses\n * @param {object} root The root element for the starred view.\n * @param {array} courses containing array of returned courses.\n * @returns {promise} Resolved with HTML and JS strings\n */\n var renderCourses = function(root, courses) {\n if (courses.length > 0) {\n return Templates.render('core_course/view-cards', {\n courses: courses\n });\n } else {\n var nocoursesimg = root.find(SELECTORS.STARRED_COURSES_REGION_VIEW).attr('data-nocoursesimg');\n return Templates.render('block_starredcourses/no-courses', {\n nocoursesimg: nocoursesimg\n });\n }\n };\n\n /**\n * Fetch user's starred courses and reload the content of the block.\n *\n * @param {object} root The root element for the starred view.\n * @returns {promise} The updated content for the block.\n */\n var reloadContent = function(root) {\n var content = root.find(SELECTORS.STARRED_COURSES_REGION);\n\n var args = {\n limit: 0,\n offset: 0,\n };\n\n return Repository.getStarredCourses(args)\n .then(function(courses) {\n // Whether the course category should be displayed in the course item.\n var showcoursecategory = $(SELECTORS.BLOCK_CONTAINER).data('displaycoursecategory');\n courses = courses.map(function(course) {\n course.showcoursecategory = showcoursecategory;\n return course;\n });\n return renderCourses(root, courses);\n }).then(function(html, js) {\n return Templates.replaceNodeContents(content, html, js);\n }).catch(Notification.exception);\n };\n\n /**\n * Register event listeners for the block.\n *\n * @param {object} root The calendar root element\n */\n var registerEventListeners = function(root) {\n PubSub.subscribe(CourseEvents.favourited, function() {\n reloadContent(root);\n });\n\n PubSub.subscribe(CourseEvents.unfavorited, function() {\n reloadContent(root);\n });\n };\n\n /**\n * Initialise all of the modules for the starred courses block.\n *\n * @param {object} root The root element for the block.\n */\n var init = function(root) {\n root = $(root);\n\n registerEventListeners(root);\n reloadContent(root);\n };\n\n return {\n init: init\n };\n});\n"],"names":["define","$","Notification","Repository","PubSub","Templates","CourseEvents","SELECTORS","reloadContent","root","content","find","getStarredCourses","limit","offset","then","courses","showcoursecategory","data","map","course","length","render","nocoursesimg","attr","renderCourses","html","js","replaceNodeContents","catch","exception","init","subscribe","favourited","unfavorited","registerEventListeners"],"mappings":";;;;;;;AAuBAA,mCACA,CACI,SACA,oBACA,kCACA,cACA,iBACA,uBAEJ,SACIC,EACAC,aACAC,WACAC,OACAC,UACAC,kBAGIC,0BACiB,kCADjBA,sCAE6B,uCAF7BA,iCAGwB,+CA8BxBC,cAAgB,SAASC,UACrBC,QAAUD,KAAKE,KAAKJ,yCAOjBJ,WAAWS,kBALP,CACPC,MAAO,EACPC,OAAQ,IAIPC,MAAK,SAASC,aAEPC,mBAAqBhB,EAAEM,2BAA2BW,KAAK,gCAC3DF,QAAUA,QAAQG,KAAI,SAASC,eAC3BA,OAAOH,mBAAqBA,mBACrBG,UAjCH,SAASX,KAAMO,YAC3BA,QAAQK,OAAS,SACVhB,UAAUiB,OAAO,yBAA0B,CAC9CN,QAASA,cAGTO,aAAed,KAAKE,KAAKJ,uCAAuCiB,KAAK,4BAClEnB,UAAUiB,OAAO,kCAAmC,CACvDC,aAAcA,eA2BPE,CAAchB,KAAMO,YAC5BD,MAAK,SAASW,KAAMC,WACZtB,UAAUuB,oBAAoBlB,QAASgB,KAAMC,OACrDE,MAAM3B,aAAa4B,kBA8BvB,CACHC,KARO,SAAStB,OAfS,SAASA,MAClCL,OAAO4B,UAAU1B,aAAa2B,YAAY,WACtCzB,cAAcC,SAGlBL,OAAO4B,UAAU1B,aAAa4B,aAAa,WACvC1B,cAAcC,UAYlB0B,CAFA1B,KAAOR,EAAEQ,OAGTD,cAAcC"}PK'9\xhͶ$starredcourses/amd/build/main.min.jsnu[/** * Javascript to initialise the starred courses block. * * @module block_starredcourses/main * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_starredcourses/main",["jquery","core/notification","block_starredcourses/repository","core/pubsub","core/templates","core_course/events"],(function($,Notification,Repository,PubSub,Templates,CourseEvents){var SELECTORS_BLOCK_CONTAINER='[data-region="starred-courses"]',SELECTORS_STARRED_COURSES_REGION_VIEW='[data-region="starred-courses-view"]',SELECTORS_STARRED_COURSES_REGION='[data-region="starred-courses-view-content"]',reloadContent=function(root){var content=root.find(SELECTORS_STARRED_COURSES_REGION);return Repository.getStarredCourses({limit:0,offset:0}).then((function(courses){var showcoursecategory=$(SELECTORS_BLOCK_CONTAINER).data("displaycoursecategory");return courses=courses.map((function(course){return course.showcoursecategory=showcoursecategory,course})),function(root,courses){if(courses.length>0)return Templates.render("core_course/view-cards",{courses:courses});var nocoursesimg=root.find(SELECTORS_STARRED_COURSES_REGION_VIEW).attr("data-nocoursesimg");return Templates.render("block_starredcourses/no-courses",{nocoursesimg:nocoursesimg})}(root,courses)})).then((function(html,js){return Templates.replaceNodeContents(content,html,js)})).catch(Notification.exception)};return{init:function(root){(function(root){PubSub.subscribe(CourseEvents.favourited,(function(){reloadContent(root)})),PubSub.subscribe(CourseEvents.unfavorited,(function(){reloadContent(root)}))})(root=$(root)),reloadContent(root)}}})); //# sourceMappingURL=main.min.js.mapPK'9\ 'starredcourses/block_starredcourses.phpnu[. /** * Starred courses block. * * @package block_starredcourses * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Starred courses block definition class. * * @package block_starredcourses * @copyright 2018 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_starredcourses extends block_base { /** * Initialises the block. * * @return void */ public function init() { $this->title = get_string('pluginname', 'block_starredcourses'); } /** * Gets the block contents. * * @return string The block HTML. */ public function get_content() { if ($this->content !== null) { return $this->content; } $renderable = new \block_starredcourses\output\main(); $renderer = $this->page->get_renderer('block_starredcourses'); $this->content = (object) [ 'text' => $renderer->render($renderable), 'footer' => '' ]; return $this->content; } /** * Locations where block can be displayed. * * @return array */ public function applicable_formats() { return array('my' => true); } /** * Allow the block to have a configuration page * * @return boolean */ public function has_config() { return true; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = get_config('block_starredcourses'); return (object) [ 'instance' => new stdClass(), 'plugin' => $configs, ]; } } PK'9\P2starredcourses/tests/behat/starred_courses.featurenu[@block_starredcourses Feature: Starred courses In order for me to quickly navigate to my favourite courses As a user I must be able to add them to the Starred courses block Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | shortname | fullname | | C1 | Course 1 | | C2 | Course 2 | | C3 | Course 3 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | starredcourses | User | student1 | my-index | content | @accessibility @javascript Scenario: User has no starred courses Given I log in as "student1" Then I should see "No starred courses" And the page should meet accessibility standards @accessibility @javascript Scenario: User has starred courses Given I am on the "My courses" page logged in as "student1" And I click on "Actions for course Course 1" "button" And I click on "Star for Course 1" "link" And I click on "Actions for course Course 3" "button" And I click on "Star for Course 3" "link" When I follow "Dashboard" Then the page should meet accessibility standards And I should see "Course 1" in the "Starred courses" "block" And I should see "Course 3" in the "Starred courses" "block" But I should not see "Course 2" in the "Starred courses" "block" PK'9\ Ԃ  starredcourses/settings.phpnu[. /** * Settings for the starredcourses block * * @package block_starredcourses * @copyright 2019 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { // Display Course Categories on the starred courses block items. $settings->add(new admin_setting_configcheckbox( 'block_starredcourses/displaycategories', get_string('displaycategories', 'block_starredcourses'), get_string('displaycategories_help', 'block_starredcourses'), 1)); } PK'9\DZ_timeline/styles.cssnu[#block-region-side-pre .block_timeline .nav-search { flex: 0 0 100%; max-width: 100%; } .block_timeline .input-group.searchbar { width: 100%; } #block-region-side-pre .block_timeline h6.event-action { flex-basis: 100%; } #block-region-side-pre .block_timeline .event-name-container { flex-basis: 50%; } #block-region-side-pre .block_timeline h6.event-action a.btn { width: auto; } .block_timeline .timeline-action-button { margin-left: auto; } @media (max-width: 480px) { .block_timeline .timeline-name { width: 100%; } .block_timeline .timeline-action-button { margin-left: 0; } } PK'9\nUtimeline/db/install.phpnu[. /** * Timeline block installation. * * @package block_timeline * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Add the timeline block to the dashboard for all users by default * when it is installed. */ function xmldb_block_timeline_install() { global $DB; if ($DB->count_records('block_instances') < 1) { // Only add the timeline block if it's being installed on an existing site. // For new sites it will be added by blocks_add_default_system_blocks(). return; } if ($defaultmypage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => 1))) { $subpagepattern = $defaultmypage->id; } else { $subpagepattern = null; } $page = new moodle_page(); $systemcontext = context_system::instance(); $page->set_context($systemcontext); // Add the block to the default /my. $page->blocks->add_region(BLOCK_POS_RIGHT); $page->blocks->add_block('timeline', BLOCK_POS_RIGHT, 0, false, 'my-index', $subpagepattern); // Now we need to find all users that have viewed their dashboard because it'll have // made duplicates of the default block_instances for them so they won't see the new // timeline block without the admin resetting all of the dashboards. // // Instead we'll just add the timeline block to their dashboards here. We will only // add the timeline block if they still have the myoverview block. $sql = "SELECT parentcontextid, subpagepattern FROM {block_instances} WHERE pagetypepattern = 'my-index' AND blockname = 'myoverview' AND parentcontextid != ?"; $params = [$systemcontext->id]; $existingrecords = $DB->get_recordset_sql($sql, $params); $blockinstances = []; $seencontexts = []; $now = time(); foreach ($existingrecords as $existingrecord) { $parentcontextid = $existingrecord->parentcontextid; if (isset($seencontexts[$parentcontextid])) { // If we've seen this context already then skip it because we don't want // to add duplicate timeline blocks to the same context. This happens // if something funny is going on with the subpagepattern. continue; } else { $seencontexts[$parentcontextid] = true; } $blockinstances[] = [ 'blockname' => 'timeline', 'parentcontextid' => $parentcontextid, 'showinsubcontexts' => false, 'pagetypepattern' => 'my-index', 'subpagepattern' => $existingrecord->subpagepattern, 'defaultregion' => BLOCK_POS_RIGHT, 'defaultweight' => 0, 'configdata' => '', 'timecreated' => $now, 'timemodified' => $now, ]; if (count($blockinstances) >= 1000) { // Insert after every 1000 records so that the memory usage doesn't // get out of control. $DB->insert_records('block_instances', $blockinstances); $blockinstances = []; } } $existingrecords->close(); if (!empty($blockinstances)) { // Insert what ever is left over. $DB->insert_records('block_instances', $blockinstances); } } PK'9\ׂtimeline/db/access.phpnu[. /** * Capabilities for the timeline block. * * @package block_timeline * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/timeline:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ) ); PK'9\@timeline/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the timeline block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @package block_timeline * @copyright 2022 Peter Dias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * * Upgrade the timeline block * @param int $oldversion * @param object $block */ function xmldb_block_timeline_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\  #timeline/lang/en/block_timeline.phpnu[. /** * Lang strings for the timeline block. * * @package block_timeline * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['ariadayfilter'] = 'Filter timeline by date'; $string['ariadayfilteroption'] = '{$a} filter option'; $string['ariaeventlistitem'] = '{$a->name} activity in {$a->course} is due on {$a->date}'; $string['ariaviewselector'] = 'Sort timeline items'; $string['ariaviewselectoroption'] = '{$a} sort option'; $string['duedate'] = 'Due date'; $string['moreactivities'] = 'Show more activities'; $string['morecourses'] = 'Show more courses'; $string['timeline:myaddinstance'] = 'Add a new timeline block to Dashboard'; $string['nocoursesinprogress'] = 'No in-progress courses'; $string['noevents'] = 'No activities require action'; $string['next30days'] = 'Next 30 days'; $string['next7days'] = 'Next 7 days'; $string['next3months'] = 'Next 3 months'; $string['next6months'] = 'Next 6 months'; $string['overdue'] = 'Overdue'; $string['all'] = 'All'; $string['pluginname'] = 'Timeline'; $string['searchevents'] = 'Search by activity type or name'; $string['sortbycourses'] = 'Sort by courses'; $string['sortbydates'] = 'Sort by dates'; $string['timeline'] = 'Timeline'; $string['viewcourse'] = 'View course'; $string['privacy:metadata:timelinesortpreference'] = 'The user sort preference for the timeline block.'; $string['privacy:metadata:timelinefilterpreference'] = 'The user day filter preference for the timeline block.'; $string['privacy:metadata:timelinelimitpreference'] = 'The user page limit preference for the timeline block.'; PK'9\timeline/pix/activities.svgnu[ PK'9\Ftimeline/pix/courses.svgnu[ PK'9\ timeline/lib.phpnu[. /** * Library functions for timeline * * @package block_timeline * @copyright 2018 Peter Dias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Define constants to store the SORT user preference */ define('BLOCK_TIMELINE_SORT_BY_DATES', 'sortbydates'); define('BLOCK_TIMELINE_SORT_BY_COURSES', 'sortbycourses'); /** * Define constants to store the FILTER user preference */ define('BLOCK_TIMELINE_FILTER_BY_NONE', 'all'); define('BLOCK_TIMELINE_FILTER_BY_OVERDUE', 'overdue'); define('BLOCK_TIMELINE_FILTER_BY_7_DAYS', 'next7days'); define('BLOCK_TIMELINE_FILTER_BY_30_DAYS', 'next30days'); define('BLOCK_TIMELINE_FILTER_BY_3_MONTHS', 'next3months'); define('BLOCK_TIMELINE_FILTER_BY_6_MONTHS', 'next6months'); define('BLOCK_TIMELINE_ACTIVITIES_LIMIT_DEFAULT', 5); /** * Returns the name of the user preferences as well as the details this plugin uses. * * @uses core_user::is_current_user * * @return array[] */ function block_timeline_user_preferences(): array { $preferences['block_timeline_user_sort_preference'] = array( 'null' => NULL_NOT_ALLOWED, 'default' => BLOCK_TIMELINE_SORT_BY_DATES, 'type' => PARAM_ALPHA, 'choices' => array(BLOCK_TIMELINE_SORT_BY_DATES, BLOCK_TIMELINE_SORT_BY_COURSES), 'permissioncallback' => [core_user::class, 'is_current_user'], ); $preferences['block_timeline_user_filter_preference'] = array( 'null' => NULL_NOT_ALLOWED, 'default' => BLOCK_TIMELINE_FILTER_BY_30_DAYS, 'type' => PARAM_ALPHANUM, 'choices' => array( BLOCK_TIMELINE_FILTER_BY_NONE, BLOCK_TIMELINE_FILTER_BY_OVERDUE, BLOCK_TIMELINE_FILTER_BY_7_DAYS, BLOCK_TIMELINE_FILTER_BY_30_DAYS, BLOCK_TIMELINE_FILTER_BY_3_MONTHS, BLOCK_TIMELINE_FILTER_BY_6_MONTHS ), 'permissioncallback' => [core_user::class, 'is_current_user'], ); $preferences['block_timeline_user_limit_preference'] = array( 'null' => NULL_NOT_ALLOWED, 'default' => BLOCK_TIMELINE_ACTIVITIES_LIMIT_DEFAULT, 'type' => PARAM_INT, 'permissioncallback' => [core_user::class, 'is_current_user'], ); return $preferences; } PK'9\<>7timeline/version.phpnu[. /** * Version details for the timeline block. * * @package block_timeline * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_timeline'; // Full name of the plugin (used for diagnostics). PK'9\Kx((%timeline/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_timeline. * * @package block_timeline * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_timeline\privacy; defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; /** * Privacy Subsystem for block_timeline. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\user_preference_provider { /** * Returns meta-data information about the myoverview block. * * @param \core_privacy\local\metadata\collection $collection A collection of meta-data. * @return \core_privacy\local\metadata\collection Return the collection of meta-data. */ public static function get_metadata(collection $collection): collection { $collection->add_user_preference('block_timeline_user_sort_preference', 'privacy:metadata:timelinesortpreference'); $collection->add_user_preference('block_timeline_user_filter_preference', 'privacy:metadata:timelinefilterpreference'); $collection->add_user_preference('block_timeline_user_limit_preference', 'privacy:metadata:timelinelimitpreference'); return $collection; } /** * Export all user preferences for the myoverview block * * @param int $userid The userid of the user whose data is to be exported. */ public static function export_user_preferences(int $userid) { $preference = get_user_preferences('block_timeline_user_sort_preference', null, $userid); if (isset($preference)) { \core_privacy\local\request\writer::export_user_preference('block_timeline', 'block_timeline_user_sort_preference', get_string($preference, 'block_timeline'), get_string('privacy:metadata:timelinesortpreference', 'block_timeline') ); } $preference = get_user_preferences('block_timeline_user_filter_preference', null, $userid); if (isset($preference)) { \core_privacy\local\request\writer::export_user_preference('block_timeline', 'block_timeline_user_filter_preference', get_string($preference, 'block_timeline'), get_string('privacy:metadata:timelinefilterpreference', 'block_timeline') ); } $preference = get_user_preferences('block_timeline_user_limit_preference', null, $userid); if (isset($preference)) { \core_privacy\local\request\writer::export_user_preference('block_timeline', 'block_timeline_user_limit_preference', $preference, get_string('privacy:metadata:timelinelimitpreference', 'block_timeline') ); } } } PK'9\Fff timeline/classes/output/main.phpnu[. /** * Class containing data for timeline block. * * @package block_timeline * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_timeline\output; defined('MOODLE_INTERNAL') || die(); use renderable; use renderer_base; use templatable; use core_course\external\course_summary_exporter; require_once($CFG->dirroot . '/course/lib.php'); require_once($CFG->dirroot . '/blocks/timeline/lib.php'); require_once($CFG->libdir . '/completionlib.php'); /** * Class containing data for timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class main implements renderable, templatable { /** Number of courses to load per page */ const COURSES_PER_PAGE = 2; /** * @var string The current filter preference */ public $filter; /** * @var string The current sort/order preference */ public $order; /** * @var string The current limit preference */ public $limit; /** @var int Number of timeline instances displayed. */ protected static $timelineinstances = 0; /** @var int This timeline instance's ID. */ protected $timelineinstanceid = 0; /** * main constructor. * * @param string $order Constant sort value from ../timeline/lib.php * @param string $filter Constant filter value from ../timeline/lib.php * @param string $limit Constant limit value from ../timeline/lib.php */ public function __construct($order, $filter, $limit) { $this->order = $order ? $order : BLOCK_TIMELINE_SORT_BY_DATES; $this->filter = $filter ? $filter : BLOCK_TIMELINE_FILTER_BY_7_DAYS; $this->limit = $limit ? $limit : BLOCK_TIMELINE_ACTIVITIES_LIMIT_DEFAULT; // Increment the timeline instances count on initialisation. self::$timelineinstances++; // Assign this instance an ID based on the latest timeline instances count. $this->timelineinstanceid = self::$timelineinstances; } /** * Test the available filters with the current user preference and return an array with * bool flags corresponding to which is active * * @return array */ protected function get_filters_as_booleans() { $filters = [ BLOCK_TIMELINE_FILTER_BY_NONE => false, BLOCK_TIMELINE_FILTER_BY_OVERDUE => false, BLOCK_TIMELINE_FILTER_BY_7_DAYS => false, BLOCK_TIMELINE_FILTER_BY_30_DAYS => false, BLOCK_TIMELINE_FILTER_BY_3_MONTHS => false, BLOCK_TIMELINE_FILTER_BY_6_MONTHS => false ]; // Set the selected filter to true. $filters[$this->filter] = true; return $filters; } /** * Get the offset/limit values corresponding to $this->filter * which are used to send through to the context as default values * * @return array */ private function get_filter_offsets() { $limit = ''; if (in_array($this->filter, [BLOCK_TIMELINE_FILTER_BY_NONE, BLOCK_TIMELINE_FILTER_BY_OVERDUE])) { $offset = -14; if ($this->filter == BLOCK_TIMELINE_FILTER_BY_OVERDUE) { $limit = 1; } } else { $offset = 0; $limit = 7; switch($this->filter) { case BLOCK_TIMELINE_FILTER_BY_30_DAYS: $limit = 30; break; case BLOCK_TIMELINE_FILTER_BY_3_MONTHS: $limit = 90; break; case BLOCK_TIMELINE_FILTER_BY_6_MONTHS: $limit = 180; break; } } return [ 'daysoffset' => $offset, 'dayslimit' => $limit ]; } /** * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output * @return array */ public function export_for_template(renderer_base $output) { $nocoursesurl = $output->image_url('courses', 'block_timeline')->out(); $noeventsurl = $output->image_url('activities', 'block_timeline')->out(); $requiredproperties = course_summary_exporter::define_properties(); $fields = join(',', array_keys($requiredproperties)); $courses = course_get_enrolled_courses_for_logged_in_user(0, 0, null, $fields); list($inprogresscourses, $processedcount) = course_filter_courses_by_timeline_classification( $courses, COURSE_TIMELINE_INPROGRESS, self::COURSES_PER_PAGE ); $formattedcourses = array_map(function($course) use ($output) { \context_helper::preload_from_record($course); $context = \context_course::instance($course->id); $exporter = new course_summary_exporter($course, ['context' => $context]); return $exporter->export($output); }, $inprogresscourses); $filters = $this->get_filters_as_booleans(); $offsets = $this->get_filter_offsets(); $contextvariables = [ 'timelineinstanceid' => $this->timelineinstanceid, 'midnight' => usergetmidnight(time()), 'coursepages' => [$formattedcourses], 'urls' => [ 'nocourses' => $nocoursesurl, 'noevents' => $noeventsurl ], 'sorttimelinedates' => $this->order == BLOCK_TIMELINE_SORT_BY_DATES, 'sorttimelinecourses' => $this->order == BLOCK_TIMELINE_SORT_BY_COURSES, 'selectedfilter' => $this->filter, 'hasdaysoffset' => true, 'hasdayslimit' => $offsets['dayslimit'] !== '' , 'nodayslimit' => $offsets['dayslimit'] === '' , 'limit' => $this->limit, 'hascourses' => !empty($formattedcourses), ]; return array_merge($contextvariables, $filters, $offsets); } } PK'9\ũK$timeline/classes/output/renderer.phpnu[. /** * Timeline block rendrer. * * @package block_timeline * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_timeline\output; defined('MOODLE_INTERNAL') || die; use plugin_renderer_base; use renderable; /** * Timeline block renderer. * * @package block_timeline * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends plugin_renderer_base { /** * Return the main content for the block timeline. * * @param main $main The main renderable * @return string HTML string */ public function render_main(main $main) { return $this->render_from_template('block_timeline/main', $main->export_for_template($this)); } } PK'9\*timeline/templates/nav-day-filter.mustachenu[{{! 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 . }} {{! @template block_timeline/nav-day-filter This template renders the day range selector for the timeline view. Example context (json): {} }} PK'9\(timeline/templates/view-courses.mustachenu[{{! 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 . }} {{! @template block_timeline/view-courses This template renders the timeline view by courses for the timeline block. Example context (json): {} }} {{#hascourses}}
            {{> block_timeline/course-item-loading-placeholder }} {{> block_timeline/course-item-loading-placeholder }}
          {{/hascourses}}
            {{> block_timeline/no-events }} {{> block_timeline/no-courses }} PK'9\ZPk timeline/templates/main.mustachenu[{{! 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 . }} {{! @template block_timeline/main This template renders the main content area for the timeline block. Example context (json): {} }}
            {{> block_timeline/nav-day-filter }}
            {{> block_timeline/nav-view-selector }}
            {{> block_timeline/view }}
            {{#js}} require( [ 'jquery', 'block_timeline/main', ], function( $, Main ) { var root = $('#block-timeline-{{uniqid}}-{{timelineinstanceid}}'); Main.init(root); }); {{/js}} PK'9\Ŀ  &timeline/templates/no-courses.mustachenu[{{! 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 . }} {{! @template block_timeline/no-courses This template renders a notice that the user has no active courses. Example context (json): { "hascourses": false, "urls": { "noevents": "#" } } }} {{^hascourses}}

            {{#str}} nocoursesinprogress, block_timeline {{/str}}

            {{/hascourses}} PK'9\̈́ y y timeline/templates/view.mustachenu[{{! 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 . }} {{! @template block_timeline/view This template renders the timeline view for the timeline block. Example context (json): { "midnight": 1538954668, "coursepages": [ {} ], "urls": { "nocourses": "#", "noevents": "#" }, "sorttimelinedates": true, "sorttimelinecourses": false, "selectedfilter": "all", "hasdaysoffset": true, "hasdayslimit": false, "nodayslimit": true, "all": true, "overdue": false, "next7days": false, "next30days": false, "next3months": false, "next6months": false, "daysoffset": -14, "dayslimit": false, "limit": 0, "hascourses": true, "overdue": false } }}
            {{> block_timeline/view-dates }}
            {{> block_timeline/view-courses }}
            PK'9\B[&timeline/templates/view-dates.mustachenu[{{! 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 . }} {{! @template block_timeline/view-dates This template renders the timeline view by dates for the timeline block. Example context (json): {} }}
            {{> block_timeline/event-list }}
            PK'9\`yc c .timeline/templates/event-list-content.mustachenu[{{! 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 . }} {{! @template block_timeline/event-list-content This template renders a group of event list items for the timeline block. Example context (json): { "events": [ { "name": "Assignment due 1", "url": "https://www.google.com", "timesort": 1490320388, "course": { "fullnamedisplay": "Course 1" }, "action": { "name": "Submit assignment", "url": "https://www.google.com", "itemcount": 1, "actionable": true }, "icon": { "key": "icon", "component": "mod_assign", "alttext": "Assignment icon" } }, { "name": "Assignment due 2", "url": "https://www.google.com", "timesort": 1490320388, "course": { "fullnamedisplay": "Course 1" }, "action": { "name": "Submit assignment", "url": "https://www.google.com", "itemcount": 1, "actionable": true }, "icon": { "key": "icon", "component": "mod_assign", "alttext": "Assignment icon" } } ] } }}
            {{#eventsbyday}}
            {{#userdate}} {{dayTimestamp}}, {{#str}} strftimedaydate, core_langconfig {{/str}} {{/userdate}}
            {{> block_timeline/event-list-items }} {{/eventsbyday}}
            PK'9\EF=J00&timeline/templates/event-list.mustachenu[{{! 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 . }} {{! @template block_timeline/event-list This template renders a list of events for the timeline block. Example context (json): { } }}
            {{#hascourses}}
              {{> block_timeline/placeholder-event-list-item }} {{> block_timeline/placeholder-event-list-item }} {{> block_timeline/placeholder-event-list-item }} {{> block_timeline/placeholder-event-list-item }} {{> block_timeline/placeholder-event-list-item }}
            {{/hascourses}}
            {{> block_timeline/no-events }} {{> block_timeline/no-courses }}
            PK'9\$ -timeline/templates/nav-view-selector.mustachenu[{{! 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 . }} {{! @template block_timeline/nav-view-selector This template renders the timeline sort selector. Example context (json): {} }}
            PK'9\}xx/timeline/templates/event-list-loadmore.mustachenu[{{! 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 . }} {{! @template block_timeline/event-list-loadmore This template renders a show more activities button. Example context (json): { "courseview": false } }}
            PK'9\`kk'timeline/templates/course-item.mustachenu[{{! 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 . }} {{! @template block_timeline/course-item This template renders the each course block containing a summary and calendar events. Example context (json): { "shortname": "course 3", "viewurl": "https://www.google.com", "summary": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout." } }}
          • {{{fullnamedisplay}}}

            {{< block_timeline/event-list }} {{$courseid}}{{id}}{{/courseid}} {{/ block_timeline/event-list }}
          • PK'9\s+timeline/templates/event-list-item.mustachenu[{{! 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 . }} {{! @template block_timeline/event-list-item This template renders an event list item for the timeline block. Example context (json): { "name": "Assignment 1 is due", "activityname": "Assignment", "activitystr": "Assignment is due", "courseview": true, "url": "https://www.google.com", "timesort": 1490320388, "course": { "fullnamedisplay": "Course 1" }, "action": { "name": "Submit assignment", "url": "https://www.google.com", "itemcount": 1, "showitemcount": true, "actionable": true }, "icon": { "key": "icon", "component": "mod_assign", "alttext": "Assignment icon", "iconurl": "#" }, "overdue": false, "purpose": "assessment" } }}
            {{#userdate}} {{timesort}}, {{#str}} strftimetime24, core_langconfig {{/str}} {{/userdate}}
            {{#icon}} {{#iconurl}} {{alttext}} {{/iconurl}} {{^iconurl}} {{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}} {{/iconurl}} {{/icon}}
            {{#overdue}}{{#str}} overdue, block_timeline {{/str}}{{/overdue}} {{{activityname}}}
            {{#courseview}} {{activitystr}} {{/courseview}} {{^courseview}} {{activitystr}}{{#course.fullnamedisplay}} · {{{course.fullnamedisplay}}}{{/course.fullnamedisplay}} {{/courseview}}
            {{#action.actionable}} {{/action.actionable}}
            PK'9\  ,timeline/templates/event-list-items.mustachenu[{{! 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 . }} {{! @template block_timeline/event-list-items This template renders a group of event list items for the timeline block. Example context (json): { "events": [ { "name": "Assignment due 1", "url": "https://www.google.com", "timesort": 1490320388, "course": { "fullnamedisplay": "Course 1" }, "action": { "name": "Submit assignment", "url": "https://www.google.com", "itemcount": 1, "actionable": true }, "icon": { "key": "icon", "component": "mod_assign", "alttext": "Assignment icon" } }, { "name": "Assignment due 2", "url": "https://www.google.com", "timesort": 1490320388, "course": { "fullnamedisplay": "Course 1" }, "action": { "name": "Submit assignment", "url": "https://www.google.com", "itemcount": 1, "actionable": true }, "icon": { "key": "icon", "component": "mod_assign", "alttext": "Assignment icon" } } ] } }}
            {{#events}} {{> block_timeline/event-list-item }} {{/events}}
            PK'9\)(timeline/templates/course-items.mustachenu[{{! 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 . }} {{! @template block_timeline/course-items This template renders the each course block containing a summary and calendar events. Example context (json): { "shortname": "course 3", "viewurl": "https://www.google.com", "summary": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout." } }} {{#courses}} {{> block_timeline/course-item }} {{/courses}} PK'9\ՠ##;timeline/templates/course-item-loading-placeholder.mustachenu[{{! 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 . }} {{! @template block_timeline/course-item-loading-placeholder This template renders the each course block containing a summary and calendar events. Example context (json): {} }}
            • {{> block_timeline/placeholder-event-list-item }} {{> block_timeline/placeholder-event-list-item }} {{> block_timeline/placeholder-event-list-item }} {{> block_timeline/placeholder-event-list-item }} {{> block_timeline/placeholder-event-list-item }}
          • PK'9\0?%timeline/templates/no-events.mustachenu[{{! 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 . }} {{! @template block_timeline/no-events This template renders confirmation that no events require action. Example context (json): { "urls": { "noevents": "#" } } }} {{#urls.noevents}} {{/urls.noevents}} PK'9\O7timeline/templates/placeholder-event-list-item.mustachenu[{{! 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 . }} {{! @template block_timeline/placeholder-event-list-item This template renders an event list item loading placeholder for the timeline block. Example context (json): {} }}
          • PK'9\j+R||&timeline/templates/nav-search.mustachenu[{{! 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 . }} {{! @template block_timeline/nav-search This template renders the main content area for the timeline block. Example context (json): {} }}
            {{< core/search_input_auto }} {{$label}}{{#str}} searchevents, block_timeline {{/str}}{{/label}} {{$placeholder}}{{#str}} searchevents, block_timeline {{/str}}{{/placeholder}} {{/ core/search_input_auto }}
            PK'9\timeline/upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This file describes API changes in the timeline block code. === 4.0 === * The timeline block courses view has been updated to only list courses which contain at least one action event within the chosen filter, so now uses \core_course\external\get_enrolled_courses_with_action_events_by_timeline_classification to fetch courses instead of core_course_external::get_enrolled_courses_by_timeline_classification, which fetches all courses within the limit. === 3.7 === * The 'block/timeline:addinstance' capability has been removed. It has never been used in code.PK'9\3timeline/block_timeline.phpnu[. /** * Contains the class for the Timeline block. * * @package block_timeline * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Timeline block class. * * @package block_timeline * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_timeline extends block_base { /** * Init. */ public function init() { $this->title = get_string('pluginname', 'block_timeline'); } /** * Returns the contents. * * @return stdClass contents of block */ public function get_content() { if (isset($this->content)) { return $this->content; } $sort = get_user_preferences('block_timeline_user_sort_preference'); $filter = get_user_preferences('block_timeline_user_filter_preference'); $limit = get_user_preferences('block_timeline_user_limit_preference'); $renderable = new \block_timeline\output\main($sort, $filter, $limit); $renderer = $this->page->get_renderer('block_timeline'); $this->content = (object) [ 'text' => $renderer->render($renderable), 'footer' => '' ]; return $this->content; } /** * Locations where block can be displayed. * * @return array */ public function applicable_formats() { return array('my' => true); } } PK'9\ۧQQ timeline/amd/src/view_courses.jsnu[// 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 . /** * Manage the timeline courses view for the timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'core/notification', 'core/custom_interaction_events', 'core/templates', 'block_timeline/event_list', 'core_course/repository', 'block_timeline/calendar_events_repository', 'core/pending' ], function( $, Notification, CustomEvents, Templates, EventList, CourseRepository, EventsRepository, Pending ) { var SELECTORS = { MORE_COURSES_BUTTON: '[data-action="more-courses"]', MORE_COURSES_BUTTON_CONTAINER: '[data-region="more-courses-button-container"]', NO_COURSES_EMPTY_MESSAGE: '[data-region="no-courses-empty-message"]', NO_COURSES_WITH_EVENTS_MESSAGE: '[data-region="no-events-empty-message"]', COURSES_LIST: '[data-region="courses-list"]', COURSE_ITEMS_LOADING_PLACEHOLDER: '[data-region="course-items-loading-placeholder"]', COURSE_EVENTS_CONTAINER: '[data-region="course-events-container"]', COURSE_NAME: '[data-region="course-name"]', LOADING_ICON: '.loading-icon', TIMELINE_BLOCK: '[data-region="timeline"]', TIMELINE_SEARCH: '[data-action="search"]' }; var TEMPLATES = { COURSE_ITEMS: 'block_timeline/course-items', LOADING_ICON: 'core/loading' }; var COURSE_CLASSIFICATION = 'all'; var COURSE_SORT = 'fullname asc'; var COURSE_EVENT_LIMIT = 5; var COURSE_LIMIT = 2; var SECONDS_IN_DAY = 60 * 60 * 24; const additionalConfig = {courseview: true}; /** * Hide the loading placeholder elements. * * @param {object} root The rool element. */ var hideLoadingPlaceholder = function(root) { root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass('hidden'); }; /** * Show the loading placeholder elements. * * @param {object} root The rool element. */ const showLoadingPlaceholder = function(root) { root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass('hidden'); }; /** * Hide the "more courses" button. * * @param {object} root The rool element. */ var hideMoreCoursesButton = function(root) { root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).addClass('hidden'); }; /** * Show the "more courses" button. * * @param {object} root The rool element. */ var showMoreCoursesButton = function(root) { root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).removeClass('hidden'); }; /** * Disable the "more courses" button and show the loading spinner. * * @param {object} root The rool element. */ var enableMoreCoursesButtonLoading = function(root) { var button = root.find(SELECTORS.MORE_COURSES_BUTTON); button.prop('disabled', true); Templates.render(TEMPLATES.LOADING_ICON, {}) .then(function(html) { button.append(html); return html; }) .catch(function() { // It's not important if this false so just do so silently. return false; }); }; /** * Enable the "more courses" button and remove the loading spinner. * * @param {object} root The rool element. */ var disableMoreCoursesButtonLoading = function(root) { var button = root.find(SELECTORS.MORE_COURSES_BUTTON); button.prop('disabled', false); button.find(SELECTORS.LOADING_ICON).remove(); }; /** * Display the message for when courses have no events available (within the current filtering). * * @param {object} root The rool element. */ const showNoCoursesWithEventsMessage = function(root) { // Remove any course list contents, since we will display the no events message. const container = root.find(SELECTORS.COURSES_LIST); Templates.replaceNodeContents(container, '', ''); root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).removeClass('hidden'); }; /** * Hide the message for when courses have no events available (within the current filtering). * * @param {object} root The rool element. */ const hideNoCoursesWithEventsMessage = function(root) { root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).addClass('hidden'); }; /** * Render the course items HTML to the page. * * @param {object} root The rool element. * @param {string} html The course items HTML to render. * @param {boolean} append Whether the HTML should be appended (eg pressed "show more courses"). * Defaults to false - replaces the existing content (eg when modifying filter values). */ var renderCourseItemsHTML = function(root, html, append = false) { var container = root.find(SELECTORS.COURSES_LIST); if (append) { Templates.appendNodeContents(container, html, ''); } else { Templates.replaceNodeContents(container, html, ''); } }; /** * Return the offset value for fetching courses. * * @param {object} root The rool element. * @return {Number} */ var getOffset = function(root) { return parseInt(root.attr('data-offset'), 10); }; /** * Set the offset value for fetching courses. * * @param {object} root The rool element. * @param {Number} offset Offset value. */ var setOffset = function(root, offset) { root.attr('data-offset', offset); }; /** * Return the limit value for fetching courses. * * @param {object} root The rool element. * @return {Number} */ var getLimit = function(root) { return parseInt(root.attr('data-limit'), 10); }; /** * Return the days offset value for fetching events. * * @param {object} root The rool element. * @return {Number} */ var getDaysOffset = function(root) { return parseInt(root.attr('data-days-offset'), 10); }; /** * Return the days limit value for fetching events. The days * limit is optional so undefined will be returned if it isn't * set. * * @param {object} root The rool element. * @return {int|undefined} */ var getDaysLimit = function(root) { var daysLimit = root.attr('data-days-limit'); return daysLimit != undefined ? parseInt(daysLimit, 10) : undefined; }; /** * Return the timestamp for the user's midnight. * * @param {object} root The rool element. * @return {Number} */ var getMidnight = function(root) { return parseInt(root.attr('data-midnight'), 10); }; /** * Return the start time for fetching events. This is calculated * based on the user's midnight value so that timezones are * preserved. * * @param {object} root The rool element. * @return {Number} */ var getStartTime = function(root) { var midnight = getMidnight(root); var daysOffset = getDaysOffset(root); return midnight + (daysOffset * SECONDS_IN_DAY); }; /** * Return the end time for fetching events. This is calculated * based on the user's midnight value so that timezones are * preserved, unless filtering by overdue, where the current UNIX timestamp is used. * * @param {object} root The rool element. * @return {Number} */ var getEndTime = function(root) { let endTime = null; if (root.attr('data-filter-overdue')) { // If filtering by overdue, end time will be the current timestamp in seconds. endTime = Math.floor(Date.now() / 1000); } else { const midnight = getMidnight(root); const daysLimit = getDaysLimit(root); if (daysLimit != undefined) { endTime = midnight + (daysLimit * SECONDS_IN_DAY); } } return endTime; }; /** * Get a list of events for the given course ids. Returns a promise that will * be resolved with the events. * * @param {array} courseIds The list of course ids to fetch events for. * @param {Number} startTime Timestamp to fetch events from. * @param {Number} limit Limit to the number of events (this applies per course, not total) * @param {Number} endTime Timestamp to fetch events to. * @param {string|undefined} searchValue Search value * @return {object} jQuery promise. */ var getEventsForCourseIds = function(courseIds, startTime, limit, endTime, searchValue) { var args = { courseids: courseIds, starttime: startTime, limit: limit }; if (endTime) { args.endtime = endTime; } if (searchValue) { args.searchvalue = searchValue; } return EventsRepository.queryByCourses(args); }; /** * Get the last time the events were reloaded. * * @param {object} root The rool element. * @return {Number} */ var getEventReloadTime = function(root) { return root.data('last-event-load-time'); }; /** * Set the last time the events were reloaded. * * @param {object} root The rool element. * @param {Number} time Timestamp in milliseconds. */ var setEventReloadTime = function(root, time) { root.data('last-event-load-time', time); }; /** * Check if events have begun reloading since the given * time. * * @param {object} root The rool element. * @param {Number} time Timestamp in milliseconds. * @return {bool} */ var hasReloadedEventsSince = function(root, time) { return getEventReloadTime(root) > time; }; /** * Send a request to the server to load the events for the courses. * * @param {array} courses List of course objects. * @param {Number} startTime Timestamp to load events after. * @param {int|undefined} endTime Timestamp to load events up until. * @param {string|undefined} searchValue Search value * @return {object} jQuery promise resolved with the events. */ var loadEventsForCourses = function(courses, startTime, endTime, searchValue) { var courseIds = courses.map(function(course) { return course.id; }); return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime, searchValue); }; /** * Render the courses in the DOM once the server has returned the courses. * * @param {array} courses List of course objects. * @param {object} root The root element * @param {Number} midnight The midnight timestamp in the user's timezone. * @param {Number} daysOffset Number of days from today to offset the events. * @param {Number} daysLimit Number of days from today to limit the events to. * @param {boolean} append Whether new content should be appended instead of replaced (eg "show more courses"). * @return {object} jQuery promise resolved after rendering is complete. */ var updateDisplayFromCourses = function(courses, root, midnight, daysOffset, daysLimit, append) { // Render the courses template. return Templates.render(TEMPLATES.COURSE_ITEMS, { courses: courses, midnight: midnight, hasdaysoffset: true, hasdayslimit: daysLimit != undefined, daysoffset: daysOffset, dayslimit: daysLimit, nodayslimit: daysLimit == undefined, courseview: true, hascourses: true }).then(function(html) { hideLoadingPlaceholder(root); if (html) { // Template rendering is complete and we have the HTML so we can // add it to the DOM. renderCourseItemsHTML(root, html, append); } return html; }) .then(function(html) { if (courses.length < COURSE_LIMIT) { // We know there aren't any more courses because we got back less // than we asked for so hide the button to request more. hideMoreCoursesButton(root); } else { // Make sure the button is visible if there are more courses to load. showMoreCoursesButton(root); } return html; }) .catch(function() { hideLoadingPlaceholder(root); }); }; /** * Find all of the visible course blocks and initialise the event * list module to being loading the events for the course block. * * @param {object} root The root element for the timeline courses view. * @param {boolean} append Whether content should be appended instead of replaced (eg "show more courses"). False by default. * @return {object} jQuery promise resolved with courses and events. */ var loadMoreCourses = function(root, append = false) { const pendingPromise = new Pending('block/timeline:load-more-courses'); var offset = getOffset(root); var limit = getLimit(root); const startTime = getStartTime(root); const endTime = getEndTime(root); const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val(); // Start loading the next set of courses. // Fetch up to limit number of courses with at least one action event in the time filtering specified. // Courses without events will also be fetched, but hidden in case they have events in other timespans. return CourseRepository.getEnrolledCoursesWithEventsByTimelineClassification( COURSE_CLASSIFICATION, limit, offset, COURSE_SORT, searchValue, startTime, endTime ).then(function(result) { var startEventLoadingTime = Date.now(); var courses = result.courses; var nextOffset = result.nextoffset; var daysOffset = getDaysOffset(root); var daysLimit = getDaysLimit(root); var midnight = getMidnight(root); const moreCoursesAvailable = result.morecoursesavailable; // Record the next offset if we want to request more courses. setOffset(root, nextOffset); // Load the events for these courses. var eventsPromise = loadEventsForCourses(courses, startTime, endTime, searchValue); // Render the courses in the DOM. var renderPromise = updateDisplayFromCourses(courses, root, midnight, daysOffset, daysLimit, append); return $.when(eventsPromise, renderPromise) .then(function(eventsByCourse) { if (hasReloadedEventsSince(root, startEventLoadingTime)) { // All of the events are being reloaded so ignore our results. return eventsByCourse; } if (courses.length > 0) { // Render the events in the correct course event list. courses.forEach(function(course) { const courseId = course.id; const containerSelector = '[data-region="course-events-container"][data-course-id="' + courseId + '"]'; const courseEventsContainer = root.find(containerSelector); const eventListRoot = courseEventsContainer.find(EventList.rootSelector); EventList.init(eventListRoot, additionalConfig); }); if (!moreCoursesAvailable) { // If no more courses with events matching the current filtering exist, hide the more courses button. hideMoreCoursesButton(root); } else { // If more courses exist with events matching the current filtering, show the more courses button. showMoreCoursesButton(root); } } else { // No more courses to load, hide the more courses button. hideMoreCoursesButton(root); // A zero offset means this was not loading "more courses", so we need to display the no results message. if (offset == 0) { showNoCoursesWithEventsMessage(root); } } return eventsByCourse; }); }).then(() => { return pendingPromise.resolve(); }).catch(Notification.exception); }; /** * Add event listeners to load more courses for the courses view. * * @param {object} root The root element for the timeline courses view. */ var registerEventListeners = function(root) { CustomEvents.define(root, [CustomEvents.events.activate]); // Show more courses and load their events when the user clicks the "more courses" button. root.on(CustomEvents.events.activate, SELECTORS.MORE_COURSES_BUTTON, function(e, data) { enableMoreCoursesButtonLoading(root); loadMoreCourses(root, true) .then(function() { disableMoreCoursesButtonLoading(root); return; }) .catch(function() { disableMoreCoursesButtonLoading(root); }); if (data) { data.originalEvent.preventDefault(); data.originalEvent.stopPropagation(); } e.stopPropagation(); }); }; /** * Initialise the timeline courses view. Begin loading the events * if this view is active. Add the relevant event listeners. * * This function should only be called once per page load because it * is adding event listeners to the page. * * @param {object} root The root element for the timeline courses view. */ var init = function(root) { root = $(root); // Only need to handle course loading if the user is actively enrolled in a course. if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) { setEventReloadTime(root, Date.now()); if (root.hasClass('active')) { // Only load if this is active otherwise it will be lazy loaded later. loadMoreCourses(root); root.attr('data-seen', true); } registerEventListeners(root); } }; /** * Reset the element back to it's initial state. Begin loading the events again * if this view is active. * * @param {object} root The root element for the timeline courses view. */ var reset = function(root) { setOffset(root, 0); showLoadingPlaceholder(root); hideNoCoursesWithEventsMessage(root); root.removeAttr('data-seen'); if (root.hasClass('active')) { shown(root); } }; /** * Begin loading the events unless we know there are no actively enrolled courses. * * @param {object} root The root element for the timeline courses view. */ var shown = function(root) { if (!root.attr('data-seen') && !root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) { loadMoreCourses(root); root.attr('data-seen', true); } }; return { init: init, reset: reset, shown: shown }; }); PK'9\IsQQtimeline/amd/src/event_list.jsnu[// 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 . /** * Javascript to load and render the list of calendar events for a * given day range. * * @module block_timeline/event_list * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'core/notification', 'core/templates', 'core/str', 'core/user_date', 'block_timeline/calendar_events_repository', 'core/pending' ], function( $, Notification, Templates, Str, UserDate, CalendarEventsRepository, Pending ) { var SECONDS_IN_DAY = 60 * 60 * 24; var courseview = false; var SELECTORS = { EMPTY_MESSAGE: '[data-region="no-events-empty-message"]', ROOT: '[data-region="event-list-container"]', EVENT_LIST_CONTENT: '[data-region="event-list-content"]', EVENT_LIST_WRAPPER: '[data-region="event-list-wrapper"]', EVENT_LIST_LOADING_PLACEHOLDER: '[data-region="event-list-loading-placeholder"]', TIMELINE_BLOCK: '[data-region="timeline"]', TIMELINE_SEARCH: '[data-action="search"]', MORE_ACTIVITIES_BUTTON: '[data-action="more-events"]', MORE_ACTIVITIES_BUTTON_CONTAINER: '[data-region="more-events-button-container"]' }; var TEMPLATES = { EVENT_LIST_CONTENT: 'block_timeline/event-list-content', MORE_ACTIVITIES_BUTTON: 'block_timeline/event-list-loadmore', LOADING_ICON: 'core/loading' }; /** @property {number} The total items will be shown on the first load. */ const DEFAULT_LAZY_LOADING_ITEMS_FIRST_LOAD = 5; /** @property {number} The total items will be shown when click on the Show more activities button. */ const DEFAULT_LAZY_LOADING_ITEMS_OTHER_LOAD = 10; /** * Hide the content area and display the empty content message. * * @param {object} root The container element */ var hideContent = function(root) { root.find(SELECTORS.EVENT_LIST_CONTENT).addClass('hidden'); root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden'); }; /** * Show the content area and hide the empty content message. * * @param {object} root The container element */ var showContent = function(root) { root.find(SELECTORS.EVENT_LIST_CONTENT).removeClass('hidden'); root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden'); }; /** * Empty the content area. * * @param {object} root The container element */ var emptyContent = function(root) { root.find(SELECTORS.EVENT_LIST_CONTENT).empty(); }; /** * Construct the template context from a list of calendar events. The events * are grouped by which day they are on. The day is calculated from the user's * midnight timestamp to ensure that the calculation is timezone agnostic. * * The return data structure will look like: * { * eventsbyday: [ * { * dayTimestamp: 1533744000, * events: [ * { ...event 1 data... }, * { ...event 2 data... } * ] * }, * { * dayTimestamp: 1533830400, * events: [ * { ...event 3 data... }, * { ...event 4 data... } * ] * } * ] * } * * Each day timestamp is the day's midnight in the user's timezone. * * @param {array} calendarEvents List of calendar events * @return {object} */ var buildTemplateContext = function(calendarEvents) { var eventsByDay = {}; var templateContext = { courseview, eventsbyday: [] }; calendarEvents.forEach(function(calendarEvent) { var dayTimestamp = calendarEvent.timeusermidnight; if (eventsByDay[dayTimestamp]) { eventsByDay[dayTimestamp].push(calendarEvent); } else { eventsByDay[dayTimestamp] = [calendarEvent]; } }); Object.keys(eventsByDay).forEach(function(dayTimestamp) { var events = eventsByDay[dayTimestamp]; templateContext.eventsbyday.push({ dayTimestamp: dayTimestamp, events: events }); }); return templateContext; }; /** * Render the HTML for the given calendar events. * * @param {array} calendarEvents A list of calendar events * @return {promise} Resolved with HTML and JS strings. */ var render = function(calendarEvents) { var templateContext = buildTemplateContext(calendarEvents); var templateName = TEMPLATES.EVENT_LIST_CONTENT; return Templates.render(templateName, templateContext); }; /** * Retrieve a list of calendar events from the server for the given * constraints. * * @param {Number} midnight The user's midnight time in unix timestamp. * @param {Number} limit Limit the result set to this number of items * @param {Number} daysOffset How many days (from midnight) to offset the results from * @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to * @param {int|false} lastId The ID of the last seen event (if any) * @param {int|undefined} courseId Course ID to restrict events to * @param {string|undefined} searchValue Search value * @return {Promise} A jquery promise */ var load = function(midnight, limit, daysOffset, daysLimit, lastId, courseId, searchValue) { var startTime = midnight + (daysOffset * SECONDS_IN_DAY); var endTime = daysLimit != undefined ? midnight + (daysLimit * SECONDS_IN_DAY) : false; var args = { starttime: startTime, limit: limit, }; if (lastId) { args.aftereventid = lastId; } if (endTime) { args.endtime = endTime; } if (searchValue) { args.searchvalue = searchValue; } if (courseId) { // If we have a course id then we only want events from that course. args.courseid = courseId; return CalendarEventsRepository.queryByCourse(args); } else { // Otherwise we want events from any course. return CalendarEventsRepository.queryByTime(args); } }; /** * Create a lazy-loading region for the calendar events in the given root element. * * @param {object} root The event list container element. * @param {object} additionalConfig Additional config options to pass to pagedContentFactory. */ var init = function(root, additionalConfig = {}) { const pendingPromise = new Pending('block/timeline:event-init'); root = $(root); courseview = !!additionalConfig.courseview; // Create a promise that will be resolved once the first set of page // data has been loaded. This ensures that the loading placeholder isn't // hidden until we have all of the data back to prevent the page elements // jumping around. var firstLoad = $.Deferred(); var eventListContent = root.find(SELECTORS.EVENT_LIST_CONTENT); var loadingPlaceholder = root.find(SELECTORS.EVENT_LIST_LOADING_PLACEHOLDER); var courseId = root.attr('data-course-id'); var daysOffset = parseInt(root.attr('data-days-offset'), 10); var daysLimit = root.attr('data-days-limit'); var midnight = parseInt(root.attr('data-midnight'), 10); const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val(); // Make sure the content area and loading placeholder is visible. // This is because the init function can be called to re-initialise // an existing event list area. emptyContent(root); showContent(root); loadingPlaceholder.removeClass('hidden'); // Days limit isn't mandatory. if (daysLimit != undefined) { daysLimit = parseInt(daysLimit, 10); } // Create the lazy loading content element. return createLazyLoadingContent(root, firstLoad, DEFAULT_LAZY_LOADING_ITEMS_FIRST_LOAD, midnight, 0, courseId, daysOffset, daysLimit, searchValue) .then(function(html, js) { firstLoad.then(function(data) { if (!data.hasContent) { loadingPlaceholder.addClass('hidden'); // If we didn't get any data then show the empty data message. return hideContent(root); } html = $(html); // Hide the content for now. html.addClass('hidden'); // Replace existing elements with the newly created lazy-loading region. Templates.replaceNodeContents(eventListContent, html, js); // Prevent changing page elements too much by only showing the content // once we've loaded some data for the first time. This allows our // fancy loading placeholder to shine. html.removeClass('hidden'); loadingPlaceholder.addClass('hidden'); if (!data.loadedAll) { Templates.render(TEMPLATES.MORE_ACTIVITIES_BUTTON, {courseview}).then(function(html) { eventListContent.append(html); setLastTimestamp(root, data.lastTimeStamp); // Init the event handler. initEventListener(root); return html; }).catch(function() { return false; }); } return data; }) .catch(function() { return false; }); return html; }).then(() => { return pendingPromise.resolve(); }) .catch(Notification.exception); }; /** * Create a lazy-loading content element for showing the event list for the initial load. * * @param {object} root The event list container element. * @param {object} firstLoad A jQuery promise to be resolved after the first set of data is loaded. * @param {int} itemLimit Limit the number of items. * @param {Number} midnight The user's midnight time in unix timestamp. * @param {int} lastId The last event ID for each loaded page. Page number is key, id is value. * @param {int|undefined} courseId Course ID to restrict events to. * @param {Number} daysOffset How many days (from midnight) to offset the results from. * @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to. * @param {string|undefined} searchValue Search value. * @return {object} jQuery promise resolved with calendar events. */ const createLazyLoadingContent = (root, firstLoad, itemLimit, midnight, lastId, courseId, daysOffset, daysLimit, searchValue) => { return loadEventsForLazyLoading( root, itemLimit, midnight, lastId, courseId, daysOffset, daysLimit, searchValue ).then(data => { if (data.calendarEvents.length) { const lastEventId = data.calendarEvents.at(-1).id; const lastTimeStamp = data.calendarEvents.at(-1).timeusermidnight; firstLoad.resolve({ hasContent: true, lastId: lastEventId, lastTimeStamp: lastTimeStamp, loadedAll: data.loadedAll }); return render(data.calendarEvents, midnight); } else { firstLoad.resolve({ hasContent: false, lastId: 0, lastTimeStamp: 0, loadedAll: true }); return data.calendarEvents; } }).catch(Notification.exception); }; /** * Handle the request from the lazy-loading region. * Uses the given data like course id, offset... to request the events from the server. * * @param {object} root The event list container element. * @param {int} itemLimit Limit the number of items. * @param {Number} midnight The user's midnight time in unix timestamp. * @param {int} lastId The last event ID for each loaded page. * @param {int|undefined} courseId Course ID to restrict events to. * @param {Number} daysOffset How many days (from midnight) to offset the results from. * @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to. * @param {string|undefined} searchValue Search value. * @return {object} jQuery promise resolved with calendar events. */ const loadEventsForLazyLoading = (root, itemLimit, midnight, lastId, courseId, daysOffset, daysLimit, searchValue) => { // Load one more than the given limit so that we can tell if there // is more content to load after this. const eventsPromise = load(midnight, itemLimit + 1, daysOffset, daysLimit, lastId, courseId, searchValue); let calendarEvents = []; let loadedAll = true; return eventsPromise.then(result => { if (!result.events.length) { return {calendarEvents, loadedAll}; } // Determine if the overdue filter is applied. const overdueFilter = document.querySelector("[data-filtername='overdue']"); const filterByOverdue = (overdueFilter && overdueFilter.getAttribute('aria-current')); calendarEvents = result.events.filter(event => { if (event.eventtype == 'open' || event.eventtype == 'opensubmission') { const dayTimestamp = UserDate.getUserMidnightForTimestamp(event.timesort, midnight); return dayTimestamp > midnight; } // When filtering by overdue, we fetch all events due today, in case any have elapsed already and are overdue. // This means if filtering by overdue, some events fetched might not be required (eg if due later today). return (!filterByOverdue || event.overdue); }); loadedAll = calendarEvents.length <= itemLimit; if (!loadedAll) { // Remove the last element from the array because it isn't // needed in this result set. calendarEvents.pop(); } if (calendarEvents.length) { const lastEventId = calendarEvents.at(-1).id; setOffset(root, lastEventId); } return {calendarEvents, loadedAll}; }); }; /** * Load new events and append to current list. * * @param {object} root The event list container element. */ const loadMoreEvents = root => { const midnight = parseInt(root.attr('data-midnight'), 10); const courseId = root.attr('data-course-id'); const daysOffset = parseInt(root.attr('data-days-offset'), 10); const daysLimit = root.attr('data-days-limit'); const lastId = getOffset(root); const eventListWrapper = root.find(SELECTORS.EVENT_LIST_WRAPPER); const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val(); const eventsPromise = loadEventsForLazyLoading( root, DEFAULT_LAZY_LOADING_ITEMS_OTHER_LOAD, midnight, lastId, courseId, daysOffset, daysLimit, searchValue ); eventsPromise.then(data => { if (data.calendarEvents.length) { const renderPromise = render(data.calendarEvents); const lastTimestamp = getLastTimestamp(root); renderPromise.then((html, js) => { html = $(html); // Remove the date heading if it has the same value as the previous one. html.find(`[data-timestamp="${lastTimestamp}"]`).remove(); Templates.appendNodeContents(eventListWrapper, html.html(), js); if (!data.loadedAll) { Templates.render(TEMPLATES.MORE_ACTIVITIES_BUTTON, {}).then(html => { eventListWrapper.append(html); setLastTimestamp(root, data.calendarEvents.at(-1).timeusermidnight); // Init the event handler. initEventListener(root); return html; }).catch(() => { return false; }); } return html; }).catch(Notification.exception); } return data; }).then(() => { return disableMoreActivitiesButtonLoading(root); }).catch(Notification.exception); }; /** * Return the offset value for lazy loading fetching. * * @param {object} element The event list container element. * @return {Number} Offset value. */ const getOffset = element => { return parseInt(element.attr('data-lazyload-offset'), 10); }; /** * Set the offset value for lazy loading fetching. * * @param {object} element The event list container element. * @param {Number} offset Offset value. */ const setOffset = (element, offset) => { element.attr('data-lazyload-offset', offset); }; /** * Return the timestamp value for lazy loading fetching. * * @param {object} element The event list container element. * @return {Number} Timestamp value. */ const getLastTimestamp = element => { return parseInt(element.attr('data-timestamp'), 10); }; /** * Set the timestamp value for lazy loading fetching. * * @param {object} element The event list container element. * @param {Number} timestamp Timestamp value. */ const setLastTimestamp = (element, timestamp) => { element.attr('data-timestamp', timestamp); }; /** * Add the "Show more activities" button and remove and loading spinner. * * @param {object} root The event list container element. */ const enableMoreActivitiesButtonLoading = root => { const loadMoreButton = root.find(SELECTORS.MORE_ACTIVITIES_BUTTON); loadMoreButton.prop('disabled', true); Templates.render(TEMPLATES.LOADING_ICON, {}).then(html => { loadMoreButton.append(html); return html; }).catch(() => { // It's not important if this false so just do so silently. return false; }); }; /** * Remove the "Show more activities" button and remove and loading spinner. * * @param {object} root The event list container element. */ const disableMoreActivitiesButtonLoading = root => { const loadMoreButtonContainer = root.find(SELECTORS.MORE_ACTIVITIES_BUTTON_CONTAINER); loadMoreButtonContainer.remove(); }; /** * Event initialise. * * @param {object} root The event list container element. */ const initEventListener = root => { const loadMoreButton = root.find(SELECTORS.MORE_ACTIVITIES_BUTTON); loadMoreButton.on('click', () => { enableMoreActivitiesButtonLoading(root); loadMoreEvents(root); }); }; return { init: init, rootSelector: SELECTORS.ROOT, }; }); PK'9\Ўw  timeline/amd/src/view.jsnu[// 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 . /** * Manage the timeline view for the timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'block_timeline/view_dates', 'block_timeline/view_courses', ], function( $, ViewDates, ViewCourses ) { var SELECTORS = { TIMELINE_DATES_VIEW: '[data-region="view-dates"]', TIMELINE_COURSES_VIEW: '[data-region="view-courses"]', }; /** * Intialise the timeline dates and courses views on page load. * This function should only be called once per page load because * it can cause event listeners to be added to the page. * * @param {object} root The root element for the timeline view. */ var init = function(root) { root = $(root); var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW); var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW); ViewDates.init(datesViewRoot); ViewCourses.init(coursesViewRoot); }; /** * Reset the timeline dates and courses views to their original * state on first page load. * * This is called when configuration has changed for the event lists * to cause them to reload their data. * * @param {object} root The root element for the timeline view. */ var reset = function(root) { var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW); var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW); ViewDates.reset(datesViewRoot); ViewCourses.reset(coursesViewRoot); }; /** * Tell the timeline dates or courses view that it has been displayed. * * This is called each time one of the views is displayed and is used to * lazy load the data within it on first load. * * @param {object} root The root element for the timeline view. */ var shown = function(root) { var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW); var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW); if (datesViewRoot.hasClass('active')) { ViewDates.shown(datesViewRoot); } else { ViewCourses.shown(coursesViewRoot); } }; return { init: init, reset: reset, shown: shown, }; }); PK'9\ytimeline/amd/src/view_dates.jsnu[// 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 . /** * Manage the timeline dates view for the timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'block_timeline/event_list', 'core/pubsub', 'core/paged_content_events' ], function( $, EventList, PubSub, PagedContentEvents ) { var SELECTORS = { EVENT_LIST_CONTAINER: '[data-region="event-list-container"]', NO_COURSES_EMPTY_MESSAGE: '[data-region="no-courses-empty-message"]', }; /** * Setup the listeners for the timeline block * * @param {string} root view dates container * @param {string} namespace The namespace for the paged content */ var registerEventListeners = function(root, namespace) { var event = namespace + PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT; PubSub.subscribe(event, function(limit) { $(root).data('limit', limit); }); }; /** * Initialise the event list and being loading the events. * * @param {object} root The root element for the timeline dates view. */ var load = function(root) { if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) { var eventListContainer = root.find(SELECTORS.EVENT_LIST_CONTAINER); var namespace = $(eventListContainer).attr('id') + "user_block_timeline" + Math.random(); registerEventListeners(root, namespace); var config = { persistentLimitKey: "block_timeline_user_limit_preference", eventNamespace: namespace }; EventList.init(eventListContainer, config); } }; /** * Initialise the timeline dates view. Begin loading the events * if this view is active. * * @param {object} root The root element for the timeline courses view. */ var init = function(root) { root = $(root); // Only need to handle events loading if the user is actively enrolled in a course and this view is active. if (root.hasClass('active') && !root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) { load(root); root.attr('data-seen', true); } }; /** * Reset the view back to it's initial state. If this view is active then * beging loading the events. * * @param {object} root The root element for the timeline courses view. */ var reset = function(root) { root.removeAttr('data-seen'); if (root.hasClass('active')) { load(root); root.attr('data-seen', true); } }; /** * Load the events if this is the first time the view is displayed. * * @param {object} root The root element for the timeline courses view. */ var shown = function(root) { if (!root.attr('data-seen')) { load(root); root.attr('data-seen', true); } }; return { init: init, reset: reset, shown: shown }; }); PK'9\)//timeline/amd/src/view_nav.jsnu[// 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 . /** * Manage the timeline view navigation for the timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import $ from 'jquery'; import * as CustomEvents from 'core/custom_interaction_events'; import * as View from 'block_timeline/view'; import * as Notification from 'core/notification'; import * as Utils from 'core/utils'; import * as UserRepository from 'core_user/repository'; import {getFirst} from 'core/normalise'; const SELECTORS = { TIMELINE_DAY_FILTER: '[data-region="day-filter"]', TIMELINE_DAY_FILTER_OPTION: '[data-from]', TIMELINE_VIEW_SELECTOR: '[data-region="view-selector"]', DATA_DAYS_OFFSET: '[data-days-offset]', DATA_DAYS_LIMIT: '[data-days-limit]', TIMELINE_SEARCH_INPUT: '[data-action="search"]', TIMELINE_SEARCH_CLEAR_ICON: '[data-action="clearsearch"]', NO_COURSES_EMPTY_MESSAGE: '[data-region="no-courses-empty-message"]', }; /** * Event listener for the day selector ("Next 7 days", "Next 30 days", etc). * * @param {object} root The root element for the timeline block * @param {object} timelineViewRoot The root element for the timeline view */ const registerTimelineDaySelector = function(root, timelineViewRoot) { const timelineDaySelectorContainer = root.find(SELECTORS.TIMELINE_DAY_FILTER); CustomEvents.define(timelineDaySelectorContainer, [CustomEvents.events.activate]); timelineDaySelectorContainer.on( CustomEvents.events.activate, SELECTORS.TIMELINE_DAY_FILTER_OPTION, function(e, data) { // Update the user preference var filtername = $(e.currentTarget).data('filtername'); var type = 'block_timeline_user_filter_preference'; UserRepository.setUserPreference(type, filtername) .catch(Notification.exception); var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION); if (option.attr('aria-current') == 'true') { // If it's already active then we don't need to do anything. return; } var daysOffset = option.attr('data-from'); var daysLimit = option.attr('data-to'); var elementsWithDaysOffset = root.find(SELECTORS.DATA_DAYS_OFFSET); elementsWithDaysOffset.attr('data-days-offset', daysOffset); if (daysLimit != undefined) { elementsWithDaysOffset.attr('data-days-limit', daysLimit); } else { elementsWithDaysOffset.removeAttr('data-days-limit'); } if (option.attr('data-filtername') === 'overdue') { elementsWithDaysOffset.attr('data-filter-overdue', true); } else { elementsWithDaysOffset.removeAttr('data-filter-overdue'); } // Reset the views to reinitialise the event lists now that we've // updated the day limits. View.reset(timelineViewRoot); data.originalEvent.preventDefault(); } ); }; /** * Event listener for the "sort" button in the timeline navigation that allows for * changing between the timeline dates and courses views. * * On a view change we tell the timeline view module that the view has been shown * so that it can handle how to display the appropriate view. * * @param {object} root The root element for the timeline block * @param {object} timelineViewRoot The root element for the timeline view */ const registerViewSelector = function(root, timelineViewRoot) { const viewSelector = root.find(SELECTORS.TIMELINE_VIEW_SELECTOR); // Listen for when the user changes tab so that we can show the first set of courses // and load their events when they request the sort by courses view for the first time. getFirst(viewSelector).querySelectorAll('[data-bs-toggle="tab"]').forEach((tab) => { tab.addEventListener('shown.bs.tab', (e) => { View.shown(timelineViewRoot); $(e.target).removeClass('active'); }); }); // Event selector for user_sort CustomEvents.define(viewSelector, [CustomEvents.events.activate]); viewSelector.on(CustomEvents.events.activate, "[data-bs-toggle='tab']", function(e) { var filtername = $(e.currentTarget).data('filtername'); var type = 'block_timeline_user_sort_preference'; UserRepository.setUserPreference(type, filtername) .catch(Notification.exception); }); }; /** * Event listener for the "search" input field in the timeline navigation that allows for * searching the activity name, course name and activity type. * * @param {object} root The root element for the timeline block * @param {object} timelineViewRoot The root element for the timeline view */ const registerSearch = (root, timelineViewRoot) => { const searchInput = root.find(SELECTORS.TIMELINE_SEARCH_INPUT); const clearSearchIcon = root.find(SELECTORS.TIMELINE_SEARCH_CLEAR_ICON); searchInput.on('input', Utils.debounce(() => { if (searchInput.val() !== '') { activeSearchState(clearSearchIcon, timelineViewRoot); } else { clearSearchState(clearSearchIcon, timelineViewRoot); } }, 1000)); clearSearchIcon.on('click', () => { searchInput.val(''); clearSearchState(clearSearchIcon, timelineViewRoot); searchInput.focus(); }); }; /** * Show the clear search icon. * * @param {object} clearSearchIcon Clear search icon element. * @param {object} timelineViewRoot The root element for the timeline view */ const activeSearchState = (clearSearchIcon, timelineViewRoot) => { clearSearchIcon.removeClass('d-none'); View.reset(timelineViewRoot); }; /** * Hide the clear search icon. * * @param {object} clearSearchIcon Clear search icon element. * @param {object} timelineViewRoot The root element for the timeline view */ const clearSearchState = (clearSearchIcon, timelineViewRoot) => { clearSearchIcon.addClass('d-none'); View.reset(timelineViewRoot); }; /** * Initialise the timeline view navigation by adding event listeners to * the navigation elements. * * @param {jQuery|HTMLElement} root The root element for the timeline block * @param {object} timelineViewRoot The root element for the timeline view */ export const init = function(root, timelineViewRoot) { root = $(root); registerViewSelector(root, timelineViewRoot); // Only need to handle filtering if the user is actively enrolled in a course. if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) { registerTimelineDaySelector(root, timelineViewRoot); registerSearch(root, timelineViewRoot); } }; PK'9\/kS##timeline/amd/src/main.jsnu[// 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 . /** * Javascript to initialise the timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'block_timeline/view_nav', 'block_timeline/view' ], function( $, ViewNav, View ) { var SELECTORS = { TIMELINE_VIEW: '[data-region="timeline-view"]' }; /** * Initialise all of the modules for the timeline block. * * @param {object} root The root element for the timeline block. */ var init = function(root) { root = $(root); var viewRoot = root.find(SELECTORS.TIMELINE_VIEW); // Initialise the timeline navigation elements. ViewNav.init(root, viewRoot); // Initialise the timeline view modules. View.init(viewRoot); }; return { init: init }; }); PK'9\_ss.timeline/amd/src/calendar_events_repository.jsnu[// 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 . /** * A javascript module to retrieve calendar events from the server. * * @module block_timeline/calendar_events_repository * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) { var DEFAULT_LIMIT = 20; /** * Retrieve a list of calendar events for the logged in user for the * given course. * * Valid args are: * int courseid Only get events for this course * int starttime Only get events after this time * int endtime Only get events before this time * int limit Limit the number of results returned * int aftereventid Offset the result set from the given id * * @method queryByCourse * @param {object} args The request arguments * @return {promise} Resolved with an array of the calendar events */ var queryByCourse = function(args) { if (!args.hasOwnProperty('limit')) { args.limit = DEFAULT_LIMIT; } args.limitnum = args.limit; delete args.limit; if (args.hasOwnProperty('starttime')) { args.timesortfrom = args.starttime; delete args.starttime; } if (args.hasOwnProperty('endtime')) { args.timesortto = args.endtime; delete args.endtime; } var request = { methodname: 'core_calendar_get_action_events_by_course', args: args }; var promise = Ajax.call([request])[0]; promise.fail(Notification.exception); return promise; }; /** * Retrieve a list of calendar events for the given courses for the * logged in user. * * Valid args are: * array courseids Get events for these courses * int starttime Only get events after this time * int endtime Only get events before this time * int limit Limit the number of results returned * * @method queryByCourses * @param {object} args The request arguments * @return {promise} Resolved with an array of the calendar events */ var queryByCourses = function(args) { if (!args.hasOwnProperty('limit')) { // This is intentionally smaller than the default limit. args.limit = 10; } args.limitnum = args.limit; delete args.limit; if (args.hasOwnProperty('starttime')) { args.timesortfrom = args.starttime; delete args.starttime; } if (args.hasOwnProperty('endtime')) { args.timesortto = args.endtime; delete args.endtime; } var request = { methodname: 'core_calendar_get_action_events_by_courses', args: args }; var promise = Ajax.call([request])[0]; promise.fail(Notification.exception); return promise; }; /** * Retrieve a list of calendar events for the logged in user after the given * time. * * Valid args are: * int starttime Only get events after this time * int endtime Only get events before this time * int limit Limit the number of results returned * int aftereventid Offset the result set from the given id * * @method queryByTime * @param {object} args The request arguments * @return {promise} Resolved with an array of the calendar events */ var queryByTime = function(args) { if (!args.hasOwnProperty('limit')) { args.limit = DEFAULT_LIMIT; } args.limitnum = args.limit; delete args.limit; if (args.hasOwnProperty('starttime')) { args.timesortfrom = args.starttime; delete args.starttime; } if (args.hasOwnProperty('endtime')) { args.timesortto = args.endtime; delete args.endtime; } // Don't show events related to courses that the user is suspended in. args.limittononsuspendedevents = true; var request = { methodname: 'core_calendar_get_action_events_by_timesort', args: args }; var promise = Ajax.call([request])[0]; promise.fail(Notification.exception); return promise; }; return { queryByTime: queryByTime, queryByCourse: queryByCourse, queryByCourses: queryByCourses, }; }); PK'9\{YW$timeline/amd/build/view_dates.min.jsnu[/** * Manage the timeline dates view for the timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_timeline/view_dates",["jquery","block_timeline/event_list","core/pubsub","core/paged_content_events"],(function($,EventList,PubSub,PagedContentEvents){var SELECTORS_EVENT_LIST_CONTAINER='[data-region="event-list-container"]',SELECTORS_NO_COURSES_EMPTY_MESSAGE='[data-region="no-courses-empty-message"]',load=function(root){if(!root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length){var eventListContainer=root.find(SELECTORS_EVENT_LIST_CONTAINER),namespace=$(eventListContainer).attr("id")+"user_block_timeline"+Math.random();!function(root,namespace){var event=namespace+PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT;PubSub.subscribe(event,(function(limit){$(root).data("limit",limit)}))}(root,namespace);var config={persistentLimitKey:"block_timeline_user_limit_preference",eventNamespace:namespace};EventList.init(eventListContainer,config)}};return{init:function(root){(root=$(root)).hasClass("active")&&!root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length&&(load(root),root.attr("data-seen",!0))},reset:function(root){root.removeAttr("data-seen"),root.hasClass("active")&&(load(root),root.attr("data-seen",!0))},shown:function(root){root.attr("data-seen")||(load(root),root.attr("data-seen",!0))}}})); //# sourceMappingURL=view_dates.min.js.mapPK'9\8B||$timeline/amd/build/event_list.min.jsnu[/** * Javascript to load and render the list of calendar events for a * given day range. * * @module block_timeline/event_list * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_timeline/event_list",["jquery","core/notification","core/templates","core/str","core/user_date","block_timeline/calendar_events_repository","core/pending"],(function($,Notification,Templates,Str,UserDate,CalendarEventsRepository,Pending){var courseview=!1,SELECTORS_EMPTY_MESSAGE='[data-region="no-events-empty-message"]',SELECTORS_EVENT_LIST_CONTENT='[data-region="event-list-content"]',SELECTORS_EVENT_LIST_WRAPPER='[data-region="event-list-wrapper"]',SELECTORS_EVENT_LIST_LOADING_PLACEHOLDER='[data-region="event-list-loading-placeholder"]',SELECTORS_TIMELINE_BLOCK='[data-region="timeline"]',SELECTORS_TIMELINE_SEARCH='[data-action="search"]',SELECTORS_MORE_ACTIVITIES_BUTTON='[data-action="more-events"]',SELECTORS_MORE_ACTIVITIES_BUTTON_CONTAINER='[data-region="more-events-button-container"]',TEMPLATES_EVENT_LIST_CONTENT="block_timeline/event-list-content",TEMPLATES_MORE_ACTIVITIES_BUTTON="block_timeline/event-list-loadmore",TEMPLATES_LOADING_ICON="core/loading";var hideContent=function(root){root.find(SELECTORS_EVENT_LIST_CONTENT).addClass("hidden"),root.find(SELECTORS_EMPTY_MESSAGE).removeClass("hidden")},showContent=function(root){root.find(SELECTORS_EVENT_LIST_CONTENT).removeClass("hidden"),root.find(SELECTORS_EMPTY_MESSAGE).addClass("hidden")},emptyContent=function(root){root.find(SELECTORS_EVENT_LIST_CONTENT).empty()},render=function(calendarEvents){var templateContext=function(calendarEvents){var eventsByDay={},templateContext={courseview:courseview,eventsbyday:[]};return calendarEvents.forEach((function(calendarEvent){var dayTimestamp=calendarEvent.timeusermidnight;eventsByDay[dayTimestamp]?eventsByDay[dayTimestamp].push(calendarEvent):eventsByDay[dayTimestamp]=[calendarEvent]})),Object.keys(eventsByDay).forEach((function(dayTimestamp){var events=eventsByDay[dayTimestamp];templateContext.eventsbyday.push({dayTimestamp:dayTimestamp,events:events})})),templateContext}(calendarEvents),templateName=TEMPLATES_EVENT_LIST_CONTENT;return Templates.render(templateName,templateContext)};const createLazyLoadingContent=(root,firstLoad,itemLimit,midnight,lastId,courseId,daysOffset,daysLimit,searchValue)=>loadEventsForLazyLoading(root,itemLimit,midnight,lastId,courseId,daysOffset,daysLimit,searchValue).then((data=>{if(data.calendarEvents.length){const lastEventId=data.calendarEvents.at(-1).id,lastTimeStamp=data.calendarEvents.at(-1).timeusermidnight;return firstLoad.resolve({hasContent:!0,lastId:lastEventId,lastTimeStamp:lastTimeStamp,loadedAll:data.loadedAll}),render(data.calendarEvents)}return firstLoad.resolve({hasContent:!1,lastId:0,lastTimeStamp:0,loadedAll:!0}),data.calendarEvents})).catch(Notification.exception),loadEventsForLazyLoading=(root,itemLimit,midnight,lastId,courseId,daysOffset,daysLimit,searchValue)=>{const eventsPromise=function(midnight,limit,daysOffset,daysLimit,lastId,courseId,searchValue){var endTime=null!=daysLimit&&midnight+86400*daysLimit,args={starttime:midnight+86400*daysOffset,limit:limit};return lastId&&(args.aftereventid=lastId),endTime&&(args.endtime=endTime),searchValue&&(args.searchvalue=searchValue),courseId?(args.courseid=courseId,CalendarEventsRepository.queryByCourse(args)):CalendarEventsRepository.queryByTime(args)}(midnight,itemLimit+1,daysOffset,daysLimit,lastId,courseId,searchValue);let calendarEvents=[],loadedAll=!0;return eventsPromise.then((result=>{if(!result.events.length)return{calendarEvents:calendarEvents,loadedAll:loadedAll};const overdueFilter=document.querySelector("[data-filtername='overdue']"),filterByOverdue=overdueFilter&&overdueFilter.getAttribute("aria-current");if(calendarEvents=result.events.filter((event=>{if("open"==event.eventtype||"opensubmission"==event.eventtype){return UserDate.getUserMidnightForTimestamp(event.timesort,midnight)>midnight}return!filterByOverdue||event.overdue})),loadedAll=calendarEvents.length<=itemLimit,loadedAll||calendarEvents.pop(),calendarEvents.length){const lastEventId=calendarEvents.at(-1).id;setOffset(root,lastEventId)}return{calendarEvents:calendarEvents,loadedAll:loadedAll}}))},getOffset=element=>parseInt(element.attr("data-lazyload-offset"),10),setOffset=(element,offset)=>{element.attr("data-lazyload-offset",offset)},getLastTimestamp=element=>parseInt(element.attr("data-timestamp"),10),setLastTimestamp=(element,timestamp)=>{element.attr("data-timestamp",timestamp)},disableMoreActivitiesButtonLoading=root=>{root.find(SELECTORS_MORE_ACTIVITIES_BUTTON_CONTAINER).remove()},initEventListener=root=>{root.find(SELECTORS_MORE_ACTIVITIES_BUTTON).on("click",(()=>{(root=>{const loadMoreButton=root.find(SELECTORS_MORE_ACTIVITIES_BUTTON);loadMoreButton.prop("disabled",!0),Templates.render(TEMPLATES_LOADING_ICON,{}).then((html=>(loadMoreButton.append(html),html))).catch((()=>!1))})(root),(root=>{const midnight=parseInt(root.attr("data-midnight"),10),courseId=root.attr("data-course-id"),daysOffset=parseInt(root.attr("data-days-offset"),10),daysLimit=root.attr("data-days-limit"),lastId=getOffset(root),eventListWrapper=root.find(SELECTORS_EVENT_LIST_WRAPPER),searchValue=root.closest(SELECTORS_TIMELINE_BLOCK).find(SELECTORS_TIMELINE_SEARCH).val();loadEventsForLazyLoading(root,10,midnight,lastId,courseId,daysOffset,daysLimit,searchValue).then((data=>{if(data.calendarEvents.length){const renderPromise=render(data.calendarEvents),lastTimestamp=getLastTimestamp(root);renderPromise.then(((html,js)=>((html=$(html)).find('[data-timestamp="'.concat(lastTimestamp,'"]')).remove(),Templates.appendNodeContents(eventListWrapper,html.html(),js),data.loadedAll||Templates.render(TEMPLATES_MORE_ACTIVITIES_BUTTON,{}).then((html=>(eventListWrapper.append(html),setLastTimestamp(root,data.calendarEvents.at(-1).timeusermidnight),initEventListener(root),html))).catch((()=>!1)),html))).catch(Notification.exception)}return data})).then((()=>disableMoreActivitiesButtonLoading(root))).catch(Notification.exception)})(root)}))};return{init:function(root){let additionalConfig=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const pendingPromise=new Pending("block/timeline:event-init");root=$(root),courseview=!!additionalConfig.courseview;var firstLoad=$.Deferred(),eventListContent=root.find(SELECTORS_EVENT_LIST_CONTENT),loadingPlaceholder=root.find(SELECTORS_EVENT_LIST_LOADING_PLACEHOLDER),courseId=root.attr("data-course-id"),daysOffset=parseInt(root.attr("data-days-offset"),10),daysLimit=root.attr("data-days-limit"),midnight=parseInt(root.attr("data-midnight"),10);const searchValue=root.closest(SELECTORS_TIMELINE_BLOCK).find(SELECTORS_TIMELINE_SEARCH).val();return emptyContent(root),showContent(root),loadingPlaceholder.removeClass("hidden"),null!=daysLimit&&(daysLimit=parseInt(daysLimit,10)),createLazyLoadingContent(root,firstLoad,5,midnight,0,courseId,daysOffset,daysLimit,searchValue).then((function(html,js){return firstLoad.then((function(data){return data.hasContent?((html=$(html)).addClass("hidden"),Templates.replaceNodeContents(eventListContent,html,js),html.removeClass("hidden"),loadingPlaceholder.addClass("hidden"),data.loadedAll||Templates.render(TEMPLATES_MORE_ACTIVITIES_BUTTON,{courseview:courseview}).then((function(html){return eventListContent.append(html),setLastTimestamp(root,data.lastTimeStamp),initEventListener(root),html})).catch((function(){return!1})),data):(loadingPlaceholder.addClass("hidden"),hideContent(root))})).catch((function(){return!1})),html})).then((()=>pendingPromise.resolve())).catch(Notification.exception)},rootSelector:'[data-region="event-list-container"]'}})); //# sourceMappingURL=event_list.min.js.mapPK'9\\|k|k(timeline/amd/build/event_list.min.js.mapnu[{"version":3,"file":"event_list.min.js","sources":["../src/event_list.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to load and render the list of calendar events for a\n * given day range.\n *\n * @module block_timeline/event_list\n * @copyright 2016 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/notification',\n 'core/templates',\n 'core/str',\n 'core/user_date',\n 'block_timeline/calendar_events_repository',\n 'core/pending'\n],\nfunction(\n $,\n Notification,\n Templates,\n Str,\n UserDate,\n CalendarEventsRepository,\n Pending\n) {\n\n var SECONDS_IN_DAY = 60 * 60 * 24;\n var courseview = false;\n\n var SELECTORS = {\n EMPTY_MESSAGE: '[data-region=\"no-events-empty-message\"]',\n ROOT: '[data-region=\"event-list-container\"]',\n EVENT_LIST_CONTENT: '[data-region=\"event-list-content\"]',\n EVENT_LIST_WRAPPER: '[data-region=\"event-list-wrapper\"]',\n EVENT_LIST_LOADING_PLACEHOLDER: '[data-region=\"event-list-loading-placeholder\"]',\n TIMELINE_BLOCK: '[data-region=\"timeline\"]',\n TIMELINE_SEARCH: '[data-action=\"search\"]',\n MORE_ACTIVITIES_BUTTON: '[data-action=\"more-events\"]',\n MORE_ACTIVITIES_BUTTON_CONTAINER: '[data-region=\"more-events-button-container\"]'\n };\n\n var TEMPLATES = {\n EVENT_LIST_CONTENT: 'block_timeline/event-list-content',\n MORE_ACTIVITIES_BUTTON: 'block_timeline/event-list-loadmore',\n LOADING_ICON: 'core/loading'\n };\n\n /** @property {number} The total items will be shown on the first load. */\n const DEFAULT_LAZY_LOADING_ITEMS_FIRST_LOAD = 5;\n /** @property {number} The total items will be shown when click on the Show more activities button. */\n const DEFAULT_LAZY_LOADING_ITEMS_OTHER_LOAD = 10;\n\n /**\n * Hide the content area and display the empty content message.\n *\n * @param {object} root The container element\n */\n var hideContent = function(root) {\n root.find(SELECTORS.EVENT_LIST_CONTENT).addClass('hidden');\n root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');\n };\n\n /**\n * Show the content area and hide the empty content message.\n *\n * @param {object} root The container element\n */\n var showContent = function(root) {\n root.find(SELECTORS.EVENT_LIST_CONTENT).removeClass('hidden');\n root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');\n };\n\n /**\n * Empty the content area.\n *\n * @param {object} root The container element\n */\n var emptyContent = function(root) {\n root.find(SELECTORS.EVENT_LIST_CONTENT).empty();\n };\n\n /**\n * Construct the template context from a list of calendar events. The events\n * are grouped by which day they are on. The day is calculated from the user's\n * midnight timestamp to ensure that the calculation is timezone agnostic.\n *\n * The return data structure will look like:\n * {\n * eventsbyday: [\n * {\n * dayTimestamp: 1533744000,\n * events: [\n * { ...event 1 data... },\n * { ...event 2 data... }\n * ]\n * },\n * {\n * dayTimestamp: 1533830400,\n * events: [\n * { ...event 3 data... },\n * { ...event 4 data... }\n * ]\n * }\n * ]\n * }\n *\n * Each day timestamp is the day's midnight in the user's timezone.\n *\n * @param {array} calendarEvents List of calendar events\n * @return {object}\n */\n var buildTemplateContext = function(calendarEvents) {\n var eventsByDay = {};\n var templateContext = {\n courseview,\n eventsbyday: []\n };\n\n calendarEvents.forEach(function(calendarEvent) {\n var dayTimestamp = calendarEvent.timeusermidnight;\n if (eventsByDay[dayTimestamp]) {\n eventsByDay[dayTimestamp].push(calendarEvent);\n } else {\n eventsByDay[dayTimestamp] = [calendarEvent];\n }\n });\n\n Object.keys(eventsByDay).forEach(function(dayTimestamp) {\n var events = eventsByDay[dayTimestamp];\n templateContext.eventsbyday.push({\n dayTimestamp: dayTimestamp,\n events: events\n });\n });\n\n return templateContext;\n };\n\n /**\n * Render the HTML for the given calendar events.\n *\n * @param {array} calendarEvents A list of calendar events\n * @return {promise} Resolved with HTML and JS strings.\n */\n var render = function(calendarEvents) {\n var templateContext = buildTemplateContext(calendarEvents);\n var templateName = TEMPLATES.EVENT_LIST_CONTENT;\n\n return Templates.render(templateName, templateContext);\n };\n\n /**\n * Retrieve a list of calendar events from the server for the given\n * constraints.\n *\n * @param {Number} midnight The user's midnight time in unix timestamp.\n * @param {Number} limit Limit the result set to this number of items\n * @param {Number} daysOffset How many days (from midnight) to offset the results from\n * @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to\n * @param {int|false} lastId The ID of the last seen event (if any)\n * @param {int|undefined} courseId Course ID to restrict events to\n * @param {string|undefined} searchValue Search value\n * @return {Promise} A jquery promise\n */\n var load = function(midnight, limit, daysOffset, daysLimit, lastId, courseId, searchValue) {\n var startTime = midnight + (daysOffset * SECONDS_IN_DAY);\n var endTime = daysLimit != undefined ? midnight + (daysLimit * SECONDS_IN_DAY) : false;\n\n var args = {\n starttime: startTime,\n limit: limit,\n };\n\n if (lastId) {\n args.aftereventid = lastId;\n }\n\n if (endTime) {\n args.endtime = endTime;\n }\n\n if (searchValue) {\n args.searchvalue = searchValue;\n }\n\n if (courseId) {\n // If we have a course id then we only want events from that course.\n args.courseid = courseId;\n return CalendarEventsRepository.queryByCourse(args);\n } else {\n // Otherwise we want events from any course.\n return CalendarEventsRepository.queryByTime(args);\n }\n };\n\n /**\n * Create a lazy-loading region for the calendar events in the given root element.\n *\n * @param {object} root The event list container element.\n * @param {object} additionalConfig Additional config options to pass to pagedContentFactory.\n */\n var init = function(root, additionalConfig = {}) {\n const pendingPromise = new Pending('block/timeline:event-init');\n root = $(root);\n\n courseview = !!additionalConfig.courseview;\n\n // Create a promise that will be resolved once the first set of page\n // data has been loaded. This ensures that the loading placeholder isn't\n // hidden until we have all of the data back to prevent the page elements\n // jumping around.\n var firstLoad = $.Deferred();\n var eventListContent = root.find(SELECTORS.EVENT_LIST_CONTENT);\n var loadingPlaceholder = root.find(SELECTORS.EVENT_LIST_LOADING_PLACEHOLDER);\n var courseId = root.attr('data-course-id');\n var daysOffset = parseInt(root.attr('data-days-offset'), 10);\n var daysLimit = root.attr('data-days-limit');\n var midnight = parseInt(root.attr('data-midnight'), 10);\n const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();\n\n // Make sure the content area and loading placeholder is visible.\n // This is because the init function can be called to re-initialise\n // an existing event list area.\n emptyContent(root);\n showContent(root);\n loadingPlaceholder.removeClass('hidden');\n\n // Days limit isn't mandatory.\n if (daysLimit != undefined) {\n daysLimit = parseInt(daysLimit, 10);\n }\n\n // Create the lazy loading content element.\n return createLazyLoadingContent(root, firstLoad,\n DEFAULT_LAZY_LOADING_ITEMS_FIRST_LOAD, midnight, 0, courseId, daysOffset, daysLimit, searchValue)\n .then(function(html, js) {\n firstLoad.then(function(data) {\n if (!data.hasContent) {\n loadingPlaceholder.addClass('hidden');\n // If we didn't get any data then show the empty data message.\n return hideContent(root);\n }\n\n html = $(html);\n // Hide the content for now.\n html.addClass('hidden');\n // Replace existing elements with the newly created lazy-loading region.\n Templates.replaceNodeContents(eventListContent, html, js);\n\n // Prevent changing page elements too much by only showing the content\n // once we've loaded some data for the first time. This allows our\n // fancy loading placeholder to shine.\n html.removeClass('hidden');\n loadingPlaceholder.addClass('hidden');\n\n if (!data.loadedAll) {\n Templates.render(TEMPLATES.MORE_ACTIVITIES_BUTTON, {courseview}).then(function(html) {\n eventListContent.append(html);\n setLastTimestamp(root, data.lastTimeStamp);\n // Init the event handler.\n initEventListener(root);\n return html;\n }).catch(function() {\n return false;\n });\n }\n\n return data;\n })\n .catch(function() {\n return false;\n });\n\n return html;\n }).then(() => {\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n };\n\n /**\n * Create a lazy-loading content element for showing the event list for the initial load.\n *\n * @param {object} root The event list container element.\n * @param {object} firstLoad A jQuery promise to be resolved after the first set of data is loaded.\n * @param {int} itemLimit Limit the number of items.\n * @param {Number} midnight The user's midnight time in unix timestamp.\n * @param {int} lastId The last event ID for each loaded page. Page number is key, id is value.\n * @param {int|undefined} courseId Course ID to restrict events to.\n * @param {Number} daysOffset How many days (from midnight) to offset the results from.\n * @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to.\n * @param {string|undefined} searchValue Search value.\n * @return {object} jQuery promise resolved with calendar events.\n */\n const createLazyLoadingContent = (root, firstLoad, itemLimit, midnight, lastId,\n courseId, daysOffset, daysLimit, searchValue) => {\n return loadEventsForLazyLoading(\n root,\n itemLimit,\n midnight,\n lastId,\n courseId,\n daysOffset,\n daysLimit,\n searchValue\n ).then(data => {\n if (data.calendarEvents.length) {\n const lastEventId = data.calendarEvents.at(-1).id;\n const lastTimeStamp = data.calendarEvents.at(-1).timeusermidnight;\n firstLoad.resolve({\n hasContent: true,\n lastId: lastEventId,\n lastTimeStamp: lastTimeStamp,\n loadedAll: data.loadedAll\n });\n return render(data.calendarEvents, midnight);\n } else {\n firstLoad.resolve({\n hasContent: false,\n lastId: 0,\n lastTimeStamp: 0,\n loadedAll: true\n });\n return data.calendarEvents;\n }\n }).catch(Notification.exception);\n };\n\n /**\n * Handle the request from the lazy-loading region.\n * Uses the given data like course id, offset... to request the events from the server.\n *\n * @param {object} root The event list container element.\n * @param {int} itemLimit Limit the number of items.\n * @param {Number} midnight The user's midnight time in unix timestamp.\n * @param {int} lastId The last event ID for each loaded page.\n * @param {int|undefined} courseId Course ID to restrict events to.\n * @param {Number} daysOffset How many days (from midnight) to offset the results from.\n * @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to.\n * @param {string|undefined} searchValue Search value.\n * @return {object} jQuery promise resolved with calendar events.\n */\n const loadEventsForLazyLoading = (root, itemLimit, midnight, lastId, courseId, daysOffset, daysLimit, searchValue) => {\n // Load one more than the given limit so that we can tell if there\n // is more content to load after this.\n const eventsPromise = load(midnight, itemLimit + 1, daysOffset, daysLimit, lastId, courseId, searchValue);\n let calendarEvents = [];\n let loadedAll = true;\n\n return eventsPromise.then(result => {\n if (!result.events.length) {\n return {calendarEvents, loadedAll};\n }\n\n // Determine if the overdue filter is applied.\n const overdueFilter = document.querySelector(\"[data-filtername='overdue']\");\n const filterByOverdue = (overdueFilter && overdueFilter.getAttribute('aria-current'));\n\n calendarEvents = result.events.filter(event => {\n if (event.eventtype == 'open' || event.eventtype == 'opensubmission') {\n const dayTimestamp = UserDate.getUserMidnightForTimestamp(event.timesort, midnight);\n return dayTimestamp > midnight;\n }\n // When filtering by overdue, we fetch all events due today, in case any have elapsed already and are overdue.\n // This means if filtering by overdue, some events fetched might not be required (eg if due later today).\n return (!filterByOverdue || event.overdue);\n });\n\n loadedAll = calendarEvents.length <= itemLimit;\n\n if (!loadedAll) {\n // Remove the last element from the array because it isn't\n // needed in this result set.\n calendarEvents.pop();\n }\n\n if (calendarEvents.length) {\n const lastEventId = calendarEvents.at(-1).id;\n setOffset(root, lastEventId);\n }\n\n return {calendarEvents, loadedAll};\n });\n };\n\n /**\n * Load new events and append to current list.\n *\n * @param {object} root The event list container element.\n */\n const loadMoreEvents = root => {\n const midnight = parseInt(root.attr('data-midnight'), 10);\n const courseId = root.attr('data-course-id');\n const daysOffset = parseInt(root.attr('data-days-offset'), 10);\n const daysLimit = root.attr('data-days-limit');\n const lastId = getOffset(root);\n const eventListWrapper = root.find(SELECTORS.EVENT_LIST_WRAPPER);\n const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();\n const eventsPromise = loadEventsForLazyLoading(\n root,\n DEFAULT_LAZY_LOADING_ITEMS_OTHER_LOAD,\n midnight,\n lastId,\n courseId,\n daysOffset,\n daysLimit,\n searchValue\n );\n eventsPromise.then(data => {\n if (data.calendarEvents.length) {\n const renderPromise = render(data.calendarEvents);\n const lastTimestamp = getLastTimestamp(root);\n renderPromise.then((html, js) => {\n html = $(html);\n\n // Remove the date heading if it has the same value as the previous one.\n html.find(`[data-timestamp=\"${lastTimestamp}\"]`).remove();\n Templates.appendNodeContents(eventListWrapper, html.html(), js);\n\n if (!data.loadedAll) {\n Templates.render(TEMPLATES.MORE_ACTIVITIES_BUTTON, {}).then(html => {\n eventListWrapper.append(html);\n setLastTimestamp(root, data.calendarEvents.at(-1).timeusermidnight);\n // Init the event handler.\n initEventListener(root);\n\n return html;\n }).catch(() => {\n return false;\n });\n }\n\n return html;\n }).catch(Notification.exception);\n }\n\n return data;\n }).then(() => {\n return disableMoreActivitiesButtonLoading(root);\n }).catch(Notification.exception);\n };\n\n /**\n * Return the offset value for lazy loading fetching.\n *\n * @param {object} element The event list container element.\n * @return {Number} Offset value.\n */\n const getOffset = element => {\n return parseInt(element.attr('data-lazyload-offset'), 10);\n };\n\n /**\n * Set the offset value for lazy loading fetching.\n *\n * @param {object} element The event list container element.\n * @param {Number} offset Offset value.\n */\n const setOffset = (element, offset) => {\n element.attr('data-lazyload-offset', offset);\n };\n\n /**\n * Return the timestamp value for lazy loading fetching.\n *\n * @param {object} element The event list container element.\n * @return {Number} Timestamp value.\n */\n const getLastTimestamp = element => {\n return parseInt(element.attr('data-timestamp'), 10);\n };\n\n /**\n * Set the timestamp value for lazy loading fetching.\n *\n * @param {object} element The event list container element.\n * @param {Number} timestamp Timestamp value.\n */\n const setLastTimestamp = (element, timestamp) => {\n element.attr('data-timestamp', timestamp);\n };\n\n /**\n * Add the \"Show more activities\" button and remove and loading spinner.\n *\n * @param {object} root The event list container element.\n */\n const enableMoreActivitiesButtonLoading = root => {\n const loadMoreButton = root.find(SELECTORS.MORE_ACTIVITIES_BUTTON);\n loadMoreButton.prop('disabled', true);\n Templates.render(TEMPLATES.LOADING_ICON, {}).then(html => {\n loadMoreButton.append(html);\n return html;\n }).catch(() => {\n // It's not important if this false so just do so silently.\n return false;\n });\n };\n\n /**\n * Remove the \"Show more activities\" button and remove and loading spinner.\n *\n * @param {object} root The event list container element.\n */\n const disableMoreActivitiesButtonLoading = root => {\n const loadMoreButtonContainer = root.find(SELECTORS.MORE_ACTIVITIES_BUTTON_CONTAINER);\n loadMoreButtonContainer.remove();\n };\n\n /**\n * Event initialise.\n *\n * @param {object} root The event list container element.\n */\n const initEventListener = root => {\n const loadMoreButton = root.find(SELECTORS.MORE_ACTIVITIES_BUTTON);\n loadMoreButton.on('click', () => {\n enableMoreActivitiesButtonLoading(root);\n loadMoreEvents(root);\n });\n };\n\n return {\n init: init,\n rootSelector: SELECTORS.ROOT,\n };\n});\n"],"names":["define","$","Notification","Templates","Str","UserDate","CalendarEventsRepository","Pending","courseview","SELECTORS","TEMPLATES","hideContent","root","find","addClass","removeClass","showContent","emptyContent","empty","render","calendarEvents","templateContext","eventsByDay","eventsbyday","forEach","calendarEvent","dayTimestamp","timeusermidnight","push","Object","keys","events","buildTemplateContext","templateName","createLazyLoadingContent","firstLoad","itemLimit","midnight","lastId","courseId","daysOffset","daysLimit","searchValue","loadEventsForLazyLoading","then","data","length","lastEventId","at","id","lastTimeStamp","resolve","hasContent","loadedAll","catch","exception","eventsPromise","limit","endTime","undefined","args","starttime","aftereventid","endtime","searchvalue","courseid","queryByCourse","queryByTime","load","result","overdueFilter","document","querySelector","filterByOverdue","getAttribute","filter","event","eventtype","getUserMidnightForTimestamp","timesort","overdue","pop","setOffset","getOffset","element","parseInt","attr","offset","getLastTimestamp","setLastTimestamp","timestamp","disableMoreActivitiesButtonLoading","remove","initEventListener","on","loadMoreButton","prop","html","append","enableMoreActivitiesButtonLoading","eventListWrapper","closest","val","renderPromise","lastTimestamp","js","appendNodeContents","loadMoreEvents","init","additionalConfig","pendingPromise","Deferred","eventListContent","loadingPlaceholder","replaceNodeContents","rootSelector"],"mappings":";;;;;;;;AAuBAA,mCACA,CACI,SACA,oBACA,iBACA,WACA,iBACA,4CACA,iBAEJ,SACIC,EACAC,aACAC,UACAC,IACAC,SACAC,yBACAC,aAIIC,YAAa,EAEbC,wBACe,0CADfA,6BAGoB,qCAHpBA,6BAIoB,qCAJpBA,yCAKgC,iDALhCA,yBAMgB,2BANhBA,0BAOiB,yBAPjBA,iCAQwB,8BARxBA,2CASkC,+CAGlCC,6BACoB,oCADpBA,iCAEwB,qCAFxBA,uBAGc,mBAadC,YAAc,SAASC,MACvBA,KAAKC,KAAKJ,8BAA8BK,SAAS,UACjDF,KAAKC,KAAKJ,yBAAyBM,YAAY,WAQ/CC,YAAc,SAASJ,MACvBA,KAAKC,KAAKJ,8BAA8BM,YAAY,UACpDH,KAAKC,KAAKJ,yBAAyBK,SAAS,WAQ5CG,aAAe,SAASL,MACxBA,KAAKC,KAAKJ,8BAA8BS,SAkExCC,OAAS,SAASC,oBACdC,gBAlCmB,SAASD,oBAC5BE,YAAc,GACdD,gBAAkB,CAClBb,WAAAA,WACAe,YAAa,WAGjBH,eAAeI,SAAQ,SAASC,mBACxBC,aAAeD,cAAcE,iBAC7BL,YAAYI,cACZJ,YAAYI,cAAcE,KAAKH,eAE/BH,YAAYI,cAAgB,CAACD,kBAIrCI,OAAOC,KAAKR,aAAaE,SAAQ,SAASE,kBAClCK,OAAST,YAAYI,cACzBL,gBAAgBE,YAAYK,KAAK,CAC7BF,aAAcA,aACdK,OAAQA,YAITV,gBAUeW,CAAqBZ,gBACvCa,aAAevB,oCAEZP,UAAUgB,OAAOc,aAAcZ,wBAkJpCa,yBAA2B,CAACtB,KAAMuB,UAAWC,UAAWC,SAAUC,OACpEC,SAAUC,WAAYC,UAAWC,cAC1BC,yBACH/B,KACAwB,UACAC,SACAC,OACAC,SACAC,WACAC,UACAC,aACFE,MAAKC,UACCA,KAAKzB,eAAe0B,OAAQ,OACtBC,YAAcF,KAAKzB,eAAe4B,IAAI,GAAGC,GACzCC,cAAgBL,KAAKzB,eAAe4B,IAAI,GAAGrB,wBACjDQ,UAAUgB,QAAQ,CACdC,YAAY,EACZd,OAAQS,YACRG,cAAeA,cACfG,UAAWR,KAAKQ,YAEblC,OAAO0B,KAAKzB,uBAEnBe,UAAUgB,QAAQ,CACdC,YAAY,EACZd,OAAQ,EACRY,cAAe,EACfG,WAAW,IAERR,KAAKzB,kBAEjBkC,MAAMpD,aAAaqD,WAiBpBZ,yBAA2B,CAAC/B,KAAMwB,UAAWC,SAAUC,OAAQC,SAAUC,WAAYC,UAAWC,qBAG5Fc,cArLC,SAASnB,SAAUoB,MAAOjB,WAAYC,UAAWH,OAAQC,SAAUG,iBAEtEgB,QAAuBC,MAAblB,WAAyBJ,SA5ItB,MA4IkCI,UAE/CmB,KAAO,CACPC,UAJYxB,SA3IC,MA2IWG,WAKxBiB,MAAOA,cAGPnB,SACAsB,KAAKE,aAAexB,QAGpBoB,UACAE,KAAKG,QAAUL,SAGfhB,cACAkB,KAAKI,YAActB,aAGnBH,UAEAqB,KAAKK,SAAW1B,SACTjC,yBAAyB4D,cAAcN,OAGvCtD,yBAAyB6D,YAAYP,MA0J1BQ,CAAK/B,SAAUD,UAAY,EAAGI,WAAYC,UAAWH,OAAQC,SAAUG,iBACzFtB,eAAiB,GACjBiC,WAAY,SAETG,cAAcZ,MAAKyB,aACjBA,OAAOtC,OAAOe,aACR,CAAC1B,eAAAA,eAAgBiC,UAAAA,iBAItBiB,cAAgBC,SAASC,cAAc,+BACvCC,gBAAmBH,eAAiBA,cAAcI,aAAa,mBAErEtD,eAAiBiD,OAAOtC,OAAO4C,QAAOC,WACX,QAAnBA,MAAMC,WAA0C,kBAAnBD,MAAMC,UAA+B,QAC7CxE,SAASyE,4BAA4BF,MAAMG,SAAU1C,UACpDA,gBAIjBoC,iBAAmBG,MAAMI,WAGtC3B,UAAYjC,eAAe0B,QAAUV,UAEhCiB,WAGDjC,eAAe6D,MAGf7D,eAAe0B,OAAQ,OACjBC,YAAc3B,eAAe4B,IAAI,GAAGC,GAC1CiC,UAAUtE,KAAMmC,mBAGb,CAAC3B,eAAAA,eAAgBiC,UAAAA,eAmE1B8B,UAAYC,SACPC,SAASD,QAAQE,KAAK,wBAAyB,IASpDJ,UAAY,CAACE,QAASG,UACxBH,QAAQE,KAAK,uBAAwBC,SASnCC,iBAAmBJ,SACdC,SAASD,QAAQE,KAAK,kBAAmB,IAS9CG,iBAAmB,CAACL,QAASM,aAC/BN,QAAQE,KAAK,iBAAkBI,YAyB7BC,mCAAqC/E,OACPA,KAAKC,KAAKJ,4CAClBmF,UAQtBC,kBAAoBjF,OACCA,KAAKC,KAAKJ,kCAClBqF,GAAG,SAAS,KA7BWlF,CAAAA,aAChCmF,eAAiBnF,KAAKC,KAAKJ,kCACjCsF,eAAeC,KAAK,YAAY,GAChC7F,UAAUgB,OAAOT,uBAAwB,IAAIkC,MAAKqD,OAC9CF,eAAeG,OAAOD,MACfA,QACR3C,OAAM,KAEE,KAsBP6C,CAAkCvF,MA/HnBA,CAAAA,aACbyB,SAAWgD,SAASzE,KAAK0E,KAAK,iBAAkB,IAChD/C,SAAW3B,KAAK0E,KAAK,kBACrB9C,WAAa6C,SAASzE,KAAK0E,KAAK,oBAAqB,IACrD7C,UAAY7B,KAAK0E,KAAK,mBACtBhD,OAAS6C,UAAUvE,MACnBwF,iBAAmBxF,KAAKC,KAAKJ,8BAC7BiC,YAAc9B,KAAKyF,QAAQ5F,0BAA0BI,KAAKJ,2BAA2B6F,MACrE3D,yBAClB/B,KA7VsC,GA+VtCyB,SACAC,OACAC,SACAC,WACAC,UACAC,aAEUE,MAAKC,UACXA,KAAKzB,eAAe0B,OAAQ,OACtByD,cAAgBpF,OAAO0B,KAAKzB,gBAC5BoF,cAAgBhB,iBAAiB5E,MACvC2F,cAAc3D,MAAK,CAACqD,KAAMQ,OACtBR,KAAOhG,EAAEgG,OAGJpF,gCAAyB2F,qBAAmBZ,SACjDzF,UAAUuG,mBAAmBN,iBAAkBH,KAAKA,OAAQQ,IAEvD5D,KAAKQ,WACNlD,UAAUgB,OAAOT,iCAAkC,IAAIkC,MAAKqD,OACxDG,iBAAiBF,OAAOD,MACxBR,iBAAiB7E,KAAMiC,KAAKzB,eAAe4B,IAAI,GAAGrB,kBAElDkE,kBAAkBjF,MAEXqF,QACR3C,OAAM,KACE,IAIR2C,QACR3C,MAAMpD,aAAaqD,kBAGnBV,QACRD,MAAK,IACG+C,mCAAmC/E,QAC3C0C,MAAMpD,aAAaqD,YA+ElBoD,CAAe/F,gBAIhB,CACHgG,KAlUO,SAAShG,UAAMiG,wEAAmB,SACnCC,eAAiB,IAAIvG,QAAQ,6BACnCK,KAAOX,EAAEW,MAETJ,aAAeqG,iBAAiBrG,eAM5B2B,UAAYlC,EAAE8G,WACdC,iBAAmBpG,KAAKC,KAAKJ,8BAC7BwG,mBAAqBrG,KAAKC,KAAKJ,0CAC/B8B,SAAW3B,KAAK0E,KAAK,kBACrB9C,WAAa6C,SAASzE,KAAK0E,KAAK,oBAAqB,IACrD7C,UAAY7B,KAAK0E,KAAK,mBACtBjD,SAAWgD,SAASzE,KAAK0E,KAAK,iBAAkB,UAC9C5C,YAAc9B,KAAKyF,QAAQ5F,0BAA0BI,KAAKJ,2BAA2B6F,aAK3FrF,aAAaL,MACbI,YAAYJ,MACZqG,mBAAmBlG,YAAY,UAGd4C,MAAblB,YACAA,UAAY4C,SAAS5C,UAAW,KAI7BP,yBAAyBtB,KAAMuB,UAzLI,EA0LCE,SAAU,EAAGE,SAAUC,WAAYC,UAAWC,aACpFE,MAAK,SAASqD,KAAMQ,WACjBtE,UAAUS,MAAK,SAASC,aACfA,KAAKO,aAMV6C,KAAOhG,EAAEgG,OAEJnF,SAAS,UAEdX,UAAU+G,oBAAoBF,iBAAkBf,KAAMQ,IAKtDR,KAAKlF,YAAY,UACjBkG,mBAAmBnG,SAAS,UAEvB+B,KAAKQ,WACNlD,UAAUgB,OAAOT,iCAAkC,CAACF,WAAAA,aAAaoC,MAAK,SAASqD,aAC3Ee,iBAAiBd,OAAOD,MACxBR,iBAAiB7E,KAAMiC,KAAKK,eAE5B2C,kBAAkBjF,MACXqF,QACR3C,OAAM,kBACE,KAIRT,OA7BHoE,mBAAmBnG,SAAS,UAErBH,YAAYC,UA6B1B0C,OAAM,kBACI,KAGJ2C,QACRrD,MAAK,IACGkE,eAAe3D,YAEzBG,MAAMpD,aAAaqD,YAuPxB4D,aA7eM"}PK'9\'ڰ/'/'&timeline/amd/build/view_nav.min.js.mapnu[{"version":3,"file":"view_nav.min.js","sources":["../src/view_nav.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Manage the timeline view navigation for the timeline block.\n *\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as CustomEvents from 'core/custom_interaction_events';\nimport * as View from 'block_timeline/view';\nimport * as Notification from 'core/notification';\nimport * as Utils from 'core/utils';\nimport * as UserRepository from 'core_user/repository';\nimport {getFirst} from 'core/normalise';\n\nconst SELECTORS = {\n TIMELINE_DAY_FILTER: '[data-region=\"day-filter\"]',\n TIMELINE_DAY_FILTER_OPTION: '[data-from]',\n TIMELINE_VIEW_SELECTOR: '[data-region=\"view-selector\"]',\n DATA_DAYS_OFFSET: '[data-days-offset]',\n DATA_DAYS_LIMIT: '[data-days-limit]',\n TIMELINE_SEARCH_INPUT: '[data-action=\"search\"]',\n TIMELINE_SEARCH_CLEAR_ICON: '[data-action=\"clearsearch\"]',\n NO_COURSES_EMPTY_MESSAGE: '[data-region=\"no-courses-empty-message\"]',\n};\n\n/**\n * Event listener for the day selector (\"Next 7 days\", \"Next 30 days\", etc).\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst registerTimelineDaySelector = function(root, timelineViewRoot) {\n const timelineDaySelectorContainer = root.find(SELECTORS.TIMELINE_DAY_FILTER);\n\n CustomEvents.define(timelineDaySelectorContainer, [CustomEvents.events.activate]);\n timelineDaySelectorContainer.on(\n CustomEvents.events.activate,\n SELECTORS.TIMELINE_DAY_FILTER_OPTION,\n function(e, data) {\n // Update the user preference\n var filtername = $(e.currentTarget).data('filtername');\n var type = 'block_timeline_user_filter_preference';\n UserRepository.setUserPreference(type, filtername)\n .catch(Notification.exception);\n\n var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION);\n\n if (option.attr('aria-current') == 'true') {\n // If it's already active then we don't need to do anything.\n return;\n }\n\n var daysOffset = option.attr('data-from');\n var daysLimit = option.attr('data-to');\n var elementsWithDaysOffset = root.find(SELECTORS.DATA_DAYS_OFFSET);\n\n elementsWithDaysOffset.attr('data-days-offset', daysOffset);\n\n if (daysLimit != undefined) {\n elementsWithDaysOffset.attr('data-days-limit', daysLimit);\n } else {\n elementsWithDaysOffset.removeAttr('data-days-limit');\n }\n\n if (option.attr('data-filtername') === 'overdue') {\n elementsWithDaysOffset.attr('data-filter-overdue', true);\n } else {\n elementsWithDaysOffset.removeAttr('data-filter-overdue');\n }\n\n // Reset the views to reinitialise the event lists now that we've\n // updated the day limits.\n View.reset(timelineViewRoot);\n\n data.originalEvent.preventDefault();\n }\n );\n};\n\n/**\n * Event listener for the \"sort\" button in the timeline navigation that allows for\n * changing between the timeline dates and courses views.\n *\n * On a view change we tell the timeline view module that the view has been shown\n * so that it can handle how to display the appropriate view.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst registerViewSelector = function(root, timelineViewRoot) {\n const viewSelector = root.find(SELECTORS.TIMELINE_VIEW_SELECTOR);\n\n // Listen for when the user changes tab so that we can show the first set of courses\n // and load their events when they request the sort by courses view for the first time.\n getFirst(viewSelector).querySelectorAll('[data-bs-toggle=\"tab\"]').forEach((tab) => {\n tab.addEventListener('shown.bs.tab', (e) => {\n View.shown(timelineViewRoot);\n $(e.target).removeClass('active');\n });\n });\n\n\n // Event selector for user_sort\n CustomEvents.define(viewSelector, [CustomEvents.events.activate]);\n viewSelector.on(CustomEvents.events.activate, \"[data-bs-toggle='tab']\", function(e) {\n var filtername = $(e.currentTarget).data('filtername');\n var type = 'block_timeline_user_sort_preference';\n UserRepository.setUserPreference(type, filtername)\n .catch(Notification.exception);\n });\n};\n\n/**\n * Event listener for the \"search\" input field in the timeline navigation that allows for\n * searching the activity name, course name and activity type.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst registerSearch = (root, timelineViewRoot) => {\n const searchInput = root.find(SELECTORS.TIMELINE_SEARCH_INPUT);\n const clearSearchIcon = root.find(SELECTORS.TIMELINE_SEARCH_CLEAR_ICON);\n searchInput.on('input', Utils.debounce(() => {\n if (searchInput.val() !== '') {\n activeSearchState(clearSearchIcon, timelineViewRoot);\n } else {\n clearSearchState(clearSearchIcon, timelineViewRoot);\n }\n }, 1000));\n clearSearchIcon.on('click', () => {\n searchInput.val('');\n clearSearchState(clearSearchIcon, timelineViewRoot);\n searchInput.focus();\n });\n};\n\n/**\n * Show the clear search icon.\n *\n * @param {object} clearSearchIcon Clear search icon element.\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst activeSearchState = (clearSearchIcon, timelineViewRoot) => {\n clearSearchIcon.removeClass('d-none');\n View.reset(timelineViewRoot);\n};\n\n/**\n * Hide the clear search icon.\n *\n * @param {object} clearSearchIcon Clear search icon element.\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst clearSearchState = (clearSearchIcon, timelineViewRoot) => {\n clearSearchIcon.addClass('d-none');\n View.reset(timelineViewRoot);\n};\n\n/**\n * Initialise the timeline view navigation by adding event listeners to\n * the navigation elements.\n *\n * @param {jQuery|HTMLElement} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nexport const init = function(root, timelineViewRoot) {\n root = $(root);\n\n registerViewSelector(root, timelineViewRoot);\n\n // Only need to handle filtering if the user is actively enrolled in a course.\n if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n registerTimelineDaySelector(root, timelineViewRoot);\n registerSearch(root, timelineViewRoot);\n }\n};\n"],"names":["SELECTORS","activeSearchState","clearSearchIcon","timelineViewRoot","removeClass","View","reset","clearSearchState","addClass","root","viewSelector","find","querySelectorAll","forEach","tab","addEventListener","e","shown","target","CustomEvents","define","events","activate","on","filtername","currentTarget","data","UserRepository","setUserPreference","catch","Notification","exception","registerViewSelector","length","timelineDaySelectorContainer","option","closest","attr","daysOffset","daysLimit","elementsWithDaysOffset","undefined","removeAttr","originalEvent","preventDefault","registerTimelineDaySelector","searchInput","Utils","debounce","val","focus","registerSearch"],"mappings":";;;;;;4yCA8BMA,8BACmB,6BADnBA,qCAE0B,cAF1BA,iCAGsB,gCAHtBA,2BAIgB,qBAJhBA,gCAMqB,yBANrBA,qCAO0B,8BAP1BA,mCAQwB,2CAwHxBC,kBAAoB,CAACC,gBAAiBC,oBACxCD,gBAAgBE,YAAY,UAC5BC,KAAKC,MAAMH,mBASTI,iBAAmB,CAACL,gBAAiBC,oBACvCD,gBAAgBM,SAAS,UACzBH,KAAKC,MAAMH,iCAUK,SAASM,KAAMN,mBA5EN,SAASM,KAAMN,wBAClCO,aAAeD,KAAKE,KAAKX,0DAItBU,cAAcE,iBAAiB,0BAA0BC,SAASC,MACvEA,IAAIC,iBAAiB,gBAAiBC,IAClCX,KAAKY,MAAMd,sCACTa,EAAEE,QAAQd,YAAY,gBAMhCe,aAAaC,OAAOV,aAAc,CAACS,aAAaE,OAAOC,WACvDZ,aAAaa,GAAGJ,aAAaE,OAAOC,SAAU,0BAA0B,SAASN,OACzEQ,YAAa,mBAAER,EAAES,eAAeC,KAAK,cAEzCC,eAAeC,kBADJ,sCAC4BJ,YAClCK,MAAMC,aAAaC,eA4D5BC,CAFAvB,MAAO,mBAAEA,MAEkBN,kBAGtBM,KAAKE,KAAKX,oCAAoCiC,SA5InB,SAASxB,KAAMN,wBACzC+B,6BAA+BzB,KAAKE,KAAKX,+BAE/CmB,aAAaC,OAAOc,6BAA8B,CAACf,aAAaE,OAAOC,WACvEY,6BAA6BX,GACzBJ,aAAaE,OAAOC,SACpBtB,sCACA,SAASgB,EAAGU,UAEJF,YAAa,mBAAER,EAAES,eAAeC,KAAK,cAEzCC,eAAeC,kBADJ,wCAC4BJ,YAClCK,MAAMC,aAAaC,eAEpBI,QAAS,mBAAEnB,EAAEE,QAAQkB,QAAQpC,yCAEE,QAA/BmC,OAAOE,KAAK,qBAKZC,WAAaH,OAAOE,KAAK,aACzBE,UAAYJ,OAAOE,KAAK,WACxBG,uBAAyB/B,KAAKE,KAAKX,4BAEvCwC,uBAAuBH,KAAK,mBAAoBC,YAE/BG,MAAbF,UACAC,uBAAuBH,KAAK,kBAAmBE,WAE/CC,uBAAuBE,WAAW,mBAGC,YAAnCP,OAAOE,KAAK,mBACZG,uBAAuBH,KAAK,uBAAuB,GAEnDG,uBAAuBE,WAAW,uBAKtCrC,KAAKC,MAAMH,kBAEXuB,KAAKiB,cAAcC,qBAkGvBC,CAA4BpC,KAAMN,kBArDnB,EAACM,KAAMN,0BACpB2C,YAAcrC,KAAKE,KAAKX,iCACxBE,gBAAkBO,KAAKE,KAAKX,sCAClC8C,YAAYvB,GAAG,QAASwB,MAAMC,UAAS,KACT,KAAtBF,YAAYG,MACZhD,kBAAkBC,gBAAiBC,kBAEnCI,iBAAiBL,gBAAiBC,oBAEvC,MACHD,gBAAgBqB,GAAG,SAAS,KACxBuB,YAAYG,IAAI,IAChB1C,iBAAiBL,gBAAiBC,kBAClC2C,YAAYI,YAyCZC,CAAe1C,KAAMN"}PK'9\E/DBB4timeline/amd/build/calendar_events_repository.min.jsnu[/** * A javascript module to retrieve calendar events from the server. * * @module block_timeline/calendar_events_repository * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_timeline/calendar_events_repository",["jquery","core/ajax","core/notification"],(function($,Ajax,Notification){return{queryByTime:function(args){args.hasOwnProperty("limit")||(args.limit=20),args.limitnum=args.limit,delete args.limit,args.hasOwnProperty("starttime")&&(args.timesortfrom=args.starttime,delete args.starttime),args.hasOwnProperty("endtime")&&(args.timesortto=args.endtime,delete args.endtime),args.limittononsuspendedevents=!0;var request={methodname:"core_calendar_get_action_events_by_timesort",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},queryByCourse:function(args){args.hasOwnProperty("limit")||(args.limit=20),args.limitnum=args.limit,delete args.limit,args.hasOwnProperty("starttime")&&(args.timesortfrom=args.starttime,delete args.starttime),args.hasOwnProperty("endtime")&&(args.timesortto=args.endtime,delete args.endtime);var request={methodname:"core_calendar_get_action_events_by_course",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},queryByCourses:function(args){args.hasOwnProperty("limit")||(args.limit=10),args.limitnum=args.limit,delete args.limit,args.hasOwnProperty("starttime")&&(args.timesortfrom=args.starttime,delete args.starttime),args.hasOwnProperty("endtime")&&(args.timesortto=args.endtime,delete args.endtime);var request={methodname:"core_calendar_get_action_events_by_courses",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise}}})); //# sourceMappingURL=calendar_events_repository.min.js.mapPK'9\ZjZj*timeline/amd/build/view_courses.min.js.mapnu[{"version":3,"file":"view_courses.min.js","sources":["../src/view_courses.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Manage the timeline courses view for the timeline block.\n *\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n[\n 'jquery',\n 'core/notification',\n 'core/custom_interaction_events',\n 'core/templates',\n 'block_timeline/event_list',\n 'core_course/repository',\n 'block_timeline/calendar_events_repository',\n 'core/pending'\n],\nfunction(\n $,\n Notification,\n CustomEvents,\n Templates,\n EventList,\n CourseRepository,\n EventsRepository,\n Pending\n) {\n\n var SELECTORS = {\n MORE_COURSES_BUTTON: '[data-action=\"more-courses\"]',\n MORE_COURSES_BUTTON_CONTAINER: '[data-region=\"more-courses-button-container\"]',\n NO_COURSES_EMPTY_MESSAGE: '[data-region=\"no-courses-empty-message\"]',\n NO_COURSES_WITH_EVENTS_MESSAGE: '[data-region=\"no-events-empty-message\"]',\n COURSES_LIST: '[data-region=\"courses-list\"]',\n COURSE_ITEMS_LOADING_PLACEHOLDER: '[data-region=\"course-items-loading-placeholder\"]',\n COURSE_EVENTS_CONTAINER: '[data-region=\"course-events-container\"]',\n COURSE_NAME: '[data-region=\"course-name\"]',\n LOADING_ICON: '.loading-icon',\n TIMELINE_BLOCK: '[data-region=\"timeline\"]',\n TIMELINE_SEARCH: '[data-action=\"search\"]'\n };\n\n var TEMPLATES = {\n COURSE_ITEMS: 'block_timeline/course-items',\n LOADING_ICON: 'core/loading'\n };\n\n var COURSE_CLASSIFICATION = 'all';\n var COURSE_SORT = 'fullname asc';\n var COURSE_EVENT_LIMIT = 5;\n var COURSE_LIMIT = 2;\n var SECONDS_IN_DAY = 60 * 60 * 24;\n\n const additionalConfig = {courseview: true};\n\n /**\n * Hide the loading placeholder elements.\n *\n * @param {object} root The rool element.\n */\n var hideLoadingPlaceholder = function(root) {\n root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass('hidden');\n };\n\n /**\n * Show the loading placeholder elements.\n *\n * @param {object} root The rool element.\n */\n const showLoadingPlaceholder = function(root) {\n root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass('hidden');\n };\n\n /**\n * Hide the \"more courses\" button.\n *\n * @param {object} root The rool element.\n */\n var hideMoreCoursesButton = function(root) {\n root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the \"more courses\" button.\n *\n * @param {object} root The rool element.\n */\n var showMoreCoursesButton = function(root) {\n root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Disable the \"more courses\" button and show the loading spinner.\n *\n * @param {object} root The rool element.\n */\n var enableMoreCoursesButtonLoading = function(root) {\n var button = root.find(SELECTORS.MORE_COURSES_BUTTON);\n button.prop('disabled', true);\n Templates.render(TEMPLATES.LOADING_ICON, {})\n .then(function(html) {\n button.append(html);\n return html;\n })\n .catch(function() {\n // It's not important if this false so just do so silently.\n return false;\n });\n };\n\n /**\n * Enable the \"more courses\" button and remove the loading spinner.\n *\n * @param {object} root The rool element.\n */\n var disableMoreCoursesButtonLoading = function(root) {\n var button = root.find(SELECTORS.MORE_COURSES_BUTTON);\n button.prop('disabled', false);\n button.find(SELECTORS.LOADING_ICON).remove();\n };\n\n /**\n * Display the message for when courses have no events available (within the current filtering).\n *\n * @param {object} root The rool element.\n */\n const showNoCoursesWithEventsMessage = function(root) {\n // Remove any course list contents, since we will display the no events message.\n const container = root.find(SELECTORS.COURSES_LIST);\n Templates.replaceNodeContents(container, '', '');\n root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).removeClass('hidden');\n };\n\n /**\n * Hide the message for when courses have no events available (within the current filtering).\n *\n * @param {object} root The rool element.\n */\n const hideNoCoursesWithEventsMessage = function(root) {\n root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).addClass('hidden');\n };\n\n /**\n * Render the course items HTML to the page.\n *\n * @param {object} root The rool element.\n * @param {string} html The course items HTML to render.\n * @param {boolean} append Whether the HTML should be appended (eg pressed \"show more courses\").\n * Defaults to false - replaces the existing content (eg when modifying filter values).\n */\n var renderCourseItemsHTML = function(root, html, append = false) {\n var container = root.find(SELECTORS.COURSES_LIST);\n\n if (append) {\n Templates.appendNodeContents(container, html, '');\n } else {\n Templates.replaceNodeContents(container, html, '');\n }\n };\n\n /**\n * Return the offset value for fetching courses.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getOffset = function(root) {\n return parseInt(root.attr('data-offset'), 10);\n };\n\n /**\n * Set the offset value for fetching courses.\n *\n * @param {object} root The rool element.\n * @param {Number} offset Offset value.\n */\n var setOffset = function(root, offset) {\n root.attr('data-offset', offset);\n };\n\n /**\n * Return the limit value for fetching courses.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getLimit = function(root) {\n return parseInt(root.attr('data-limit'), 10);\n };\n\n /**\n * Return the days offset value for fetching events.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getDaysOffset = function(root) {\n return parseInt(root.attr('data-days-offset'), 10);\n };\n\n /**\n * Return the days limit value for fetching events. The days\n * limit is optional so undefined will be returned if it isn't\n * set.\n *\n * @param {object} root The rool element.\n * @return {int|undefined}\n */\n var getDaysLimit = function(root) {\n var daysLimit = root.attr('data-days-limit');\n return daysLimit != undefined ? parseInt(daysLimit, 10) : undefined;\n };\n\n /**\n * Return the timestamp for the user's midnight.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getMidnight = function(root) {\n return parseInt(root.attr('data-midnight'), 10);\n };\n\n /**\n * Return the start time for fetching events. This is calculated\n * based on the user's midnight value so that timezones are\n * preserved.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getStartTime = function(root) {\n var midnight = getMidnight(root);\n var daysOffset = getDaysOffset(root);\n return midnight + (daysOffset * SECONDS_IN_DAY);\n };\n\n /**\n * Return the end time for fetching events. This is calculated\n * based on the user's midnight value so that timezones are\n * preserved, unless filtering by overdue, where the current UNIX timestamp is used.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getEndTime = function(root) {\n let endTime = null;\n\n if (root.attr('data-filter-overdue')) {\n // If filtering by overdue, end time will be the current timestamp in seconds.\n endTime = Math.floor(Date.now() / 1000);\n } else {\n const midnight = getMidnight(root);\n const daysLimit = getDaysLimit(root);\n\n if (daysLimit != undefined) {\n endTime = midnight + (daysLimit * SECONDS_IN_DAY);\n }\n }\n\n return endTime;\n };\n\n /**\n * Get a list of events for the given course ids. Returns a promise that will\n * be resolved with the events.\n *\n * @param {array} courseIds The list of course ids to fetch events for.\n * @param {Number} startTime Timestamp to fetch events from.\n * @param {Number} limit Limit to the number of events (this applies per course, not total)\n * @param {Number} endTime Timestamp to fetch events to.\n * @param {string|undefined} searchValue Search value\n * @return {object} jQuery promise.\n */\n var getEventsForCourseIds = function(courseIds, startTime, limit, endTime, searchValue) {\n var args = {\n courseids: courseIds,\n starttime: startTime,\n limit: limit\n };\n\n if (endTime) {\n args.endtime = endTime;\n }\n\n if (searchValue) {\n args.searchvalue = searchValue;\n }\n\n return EventsRepository.queryByCourses(args);\n };\n\n /**\n * Get the last time the events were reloaded.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getEventReloadTime = function(root) {\n return root.data('last-event-load-time');\n };\n\n /**\n * Set the last time the events were reloaded.\n *\n * @param {object} root The rool element.\n * @param {Number} time Timestamp in milliseconds.\n */\n var setEventReloadTime = function(root, time) {\n root.data('last-event-load-time', time);\n };\n\n /**\n * Check if events have begun reloading since the given\n * time.\n *\n * @param {object} root The rool element.\n * @param {Number} time Timestamp in milliseconds.\n * @return {bool}\n */\n var hasReloadedEventsSince = function(root, time) {\n return getEventReloadTime(root) > time;\n };\n\n /**\n * Send a request to the server to load the events for the courses.\n *\n * @param {array} courses List of course objects.\n * @param {Number} startTime Timestamp to load events after.\n * @param {int|undefined} endTime Timestamp to load events up until.\n * @param {string|undefined} searchValue Search value\n * @return {object} jQuery promise resolved with the events.\n */\n var loadEventsForCourses = function(courses, startTime, endTime, searchValue) {\n var courseIds = courses.map(function(course) {\n return course.id;\n });\n\n return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime, searchValue);\n };\n\n /**\n * Render the courses in the DOM once the server has returned the courses.\n *\n * @param {array} courses List of course objects.\n * @param {object} root The root element\n * @param {Number} midnight The midnight timestamp in the user's timezone.\n * @param {Number} daysOffset Number of days from today to offset the events.\n * @param {Number} daysLimit Number of days from today to limit the events to.\n * @param {boolean} append Whether new content should be appended instead of replaced (eg \"show more courses\").\n * @return {object} jQuery promise resolved after rendering is complete.\n */\n var updateDisplayFromCourses = function(courses, root, midnight, daysOffset, daysLimit, append) {\n // Render the courses template.\n return Templates.render(TEMPLATES.COURSE_ITEMS, {\n courses: courses,\n midnight: midnight,\n hasdaysoffset: true,\n hasdayslimit: daysLimit != undefined,\n daysoffset: daysOffset,\n dayslimit: daysLimit,\n nodayslimit: daysLimit == undefined,\n courseview: true,\n hascourses: true\n }).then(function(html) {\n hideLoadingPlaceholder(root);\n\n if (html) {\n // Template rendering is complete and we have the HTML so we can\n // add it to the DOM.\n renderCourseItemsHTML(root, html, append);\n }\n\n return html;\n })\n .then(function(html) {\n if (courses.length < COURSE_LIMIT) {\n // We know there aren't any more courses because we got back less\n // than we asked for so hide the button to request more.\n hideMoreCoursesButton(root);\n } else {\n // Make sure the button is visible if there are more courses to load.\n showMoreCoursesButton(root);\n }\n\n return html;\n })\n .catch(function() {\n hideLoadingPlaceholder(root);\n });\n };\n\n /**\n * Find all of the visible course blocks and initialise the event\n * list module to being loading the events for the course block.\n *\n * @param {object} root The root element for the timeline courses view.\n * @param {boolean} append Whether content should be appended instead of replaced (eg \"show more courses\"). False by default.\n * @return {object} jQuery promise resolved with courses and events.\n */\n var loadMoreCourses = function(root, append = false) {\n const pendingPromise = new Pending('block/timeline:load-more-courses');\n var offset = getOffset(root);\n var limit = getLimit(root);\n const startTime = getStartTime(root);\n const endTime = getEndTime(root);\n const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();\n\n // Start loading the next set of courses.\n // Fetch up to limit number of courses with at least one action event in the time filtering specified.\n // Courses without events will also be fetched, but hidden in case they have events in other timespans.\n return CourseRepository.getEnrolledCoursesWithEventsByTimelineClassification(\n COURSE_CLASSIFICATION,\n limit,\n offset,\n COURSE_SORT,\n searchValue,\n startTime,\n endTime\n ).then(function(result) {\n var startEventLoadingTime = Date.now();\n var courses = result.courses;\n var nextOffset = result.nextoffset;\n var daysOffset = getDaysOffset(root);\n var daysLimit = getDaysLimit(root);\n var midnight = getMidnight(root);\n const moreCoursesAvailable = result.morecoursesavailable;\n\n // Record the next offset if we want to request more courses.\n setOffset(root, nextOffset);\n // Load the events for these courses.\n var eventsPromise = loadEventsForCourses(courses, startTime, endTime, searchValue);\n // Render the courses in the DOM.\n var renderPromise = updateDisplayFromCourses(courses, root, midnight, daysOffset, daysLimit, append);\n\n return $.when(eventsPromise, renderPromise)\n .then(function(eventsByCourse) {\n if (hasReloadedEventsSince(root, startEventLoadingTime)) {\n // All of the events are being reloaded so ignore our results.\n return eventsByCourse;\n }\n\n if (courses.length > 0) {\n // Render the events in the correct course event list.\n courses.forEach(function(course) {\n const courseId = course.id;\n const containerSelector = '[data-region=\"course-events-container\"][data-course-id=\"' + courseId + '\"]';\n const courseEventsContainer = root.find(containerSelector);\n const eventListRoot = courseEventsContainer.find(EventList.rootSelector);\n\n EventList.init(eventListRoot, additionalConfig);\n });\n\n if (!moreCoursesAvailable) {\n // If no more courses with events matching the current filtering exist, hide the more courses button.\n hideMoreCoursesButton(root);\n } else {\n // If more courses exist with events matching the current filtering, show the more courses button.\n showMoreCoursesButton(root);\n }\n } else {\n // No more courses to load, hide the more courses button.\n hideMoreCoursesButton(root);\n\n // A zero offset means this was not loading \"more courses\", so we need to display the no results message.\n if (offset == 0) {\n showNoCoursesWithEventsMessage(root);\n }\n }\n\n return eventsByCourse;\n });\n }).then(() => {\n return pendingPromise.resolve();\n }).catch(Notification.exception);\n };\n\n /**\n * Add event listeners to load more courses for the courses view.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var registerEventListeners = function(root) {\n CustomEvents.define(root, [CustomEvents.events.activate]);\n // Show more courses and load their events when the user clicks the \"more courses\" button.\n root.on(CustomEvents.events.activate, SELECTORS.MORE_COURSES_BUTTON, function(e, data) {\n enableMoreCoursesButtonLoading(root);\n loadMoreCourses(root, true)\n .then(function() {\n disableMoreCoursesButtonLoading(root);\n return;\n })\n .catch(function() {\n disableMoreCoursesButtonLoading(root);\n });\n\n if (data) {\n data.originalEvent.preventDefault();\n data.originalEvent.stopPropagation();\n }\n e.stopPropagation();\n });\n };\n\n /**\n * Initialise the timeline courses view. Begin loading the events\n * if this view is active. Add the relevant event listeners.\n *\n * This function should only be called once per page load because it\n * is adding event listeners to the page.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var init = function(root) {\n root = $(root);\n\n // Only need to handle course loading if the user is actively enrolled in a course.\n if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n setEventReloadTime(root, Date.now());\n\n if (root.hasClass('active')) {\n // Only load if this is active otherwise it will be lazy loaded later.\n loadMoreCourses(root);\n root.attr('data-seen', true);\n }\n\n registerEventListeners(root);\n }\n };\n\n /**\n * Reset the element back to it's initial state. Begin loading the events again\n * if this view is active.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var reset = function(root) {\n\n setOffset(root, 0);\n showLoadingPlaceholder(root);\n hideNoCoursesWithEventsMessage(root);\n root.removeAttr('data-seen');\n\n if (root.hasClass('active')) {\n shown(root);\n }\n };\n\n /**\n * Begin loading the events unless we know there are no actively enrolled courses.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var shown = function(root) {\n if (!root.attr('data-seen') && !root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n loadMoreCourses(root);\n root.attr('data-seen', true);\n }\n };\n\n return {\n init: init,\n reset: reset,\n shown: shown\n };\n});\n"],"names":["define","$","Notification","CustomEvents","Templates","EventList","CourseRepository","EventsRepository","Pending","SELECTORS","TEMPLATES","additionalConfig","courseview","hideLoadingPlaceholder","root","find","addClass","hideMoreCoursesButton","showMoreCoursesButton","removeClass","disableMoreCoursesButtonLoading","button","prop","remove","showNoCoursesWithEventsMessage","container","replaceNodeContents","getOffset","parseInt","attr","setOffset","offset","getLimit","getDaysOffset","getDaysLimit","daysLimit","undefined","getMidnight","getStartTime","getEndTime","endTime","Math","floor","Date","now","midnight","hasReloadedEventsSince","time","data","getEventReloadTime","loadEventsForCourses","courses","startTime","searchValue","courseIds","limit","args","courseids","starttime","endtime","searchvalue","queryByCourses","getEventsForCourseIds","map","course","id","COURSE_EVENT_LIMIT","updateDisplayFromCourses","daysOffset","append","render","hasdaysoffset","hasdayslimit","daysoffset","dayslimit","nodayslimit","hascourses","then","html","appendNodeContents","renderCourseItemsHTML","length","catch","loadMoreCourses","pendingPromise","closest","val","getEnrolledCoursesWithEventsByTimelineClassification","result","startEventLoadingTime","nextOffset","nextoffset","moreCoursesAvailable","morecoursesavailable","eventsPromise","renderPromise","when","eventsByCourse","forEach","containerSelector","eventListRoot","rootSelector","init","resolve","exception","registerEventListeners","events","activate","on","e","enableMoreCoursesButtonLoading","originalEvent","preventDefault","stopPropagation","shown","setEventReloadTime","hasClass","reset","showLoadingPlaceholder","hideNoCoursesWithEventsMessage","removeAttr"],"mappings":";;;;;;AAsBAA,qCACA,CACI,SACA,oBACA,iCACA,iBACA,4BACA,yBACA,4CACA,iBAEJ,SACIC,EACAC,aACAC,aACAC,UACAC,UACAC,iBACAC,iBACAC,aAGIC,8BACqB,+BADrBA,wCAE+B,gDAF/BA,mCAG0B,2CAH1BA,yCAIgC,0CAJhCA,uBAKc,+BALdA,2CAMkC,mDANlCA,uBASc,gBATdA,yBAUgB,2BAVhBA,0BAWiB,yBAGjBC,uBACc,8BADdA,uBAEc,qBASZC,iBAAmB,CAACC,YAAY,OAOlCC,uBAAyB,SAASC,MAClCA,KAAKC,KAAKN,4CAA4CO,SAAS,eAiB/DC,sBAAwB,SAASH,MACjCA,KAAKC,KAAKN,yCAAyCO,SAAS,WAQ5DE,sBAAwB,SAASJ,MACjCA,KAAKC,KAAKN,yCAAyCU,YAAY,WA2B/DC,gCAAkC,SAASN,UACvCO,OAASP,KAAKC,KAAKN,+BACvBY,OAAOC,KAAK,YAAY,GACxBD,OAAON,KAAKN,wBAAwBc,gBAQlCC,+BAAiC,SAASV,YAEtCW,UAAYX,KAAKC,KAAKN,wBAC5BL,UAAUsB,oBAAoBD,UAAW,GAAI,IAC7CX,KAAKC,KAAKN,0CAA0CU,YAAY,eAoChEQ,UAAY,SAASb,aACdc,SAASd,KAAKe,KAAK,eAAgB,KAS1CC,UAAY,SAAShB,KAAMiB,QAC3BjB,KAAKe,KAAK,cAAeE,SASzBC,SAAW,SAASlB,aACbc,SAASd,KAAKe,KAAK,cAAe,KASzCI,cAAgB,SAASnB,aAClBc,SAASd,KAAKe,KAAK,oBAAqB,KAW/CK,aAAe,SAASpB,UACpBqB,UAAYrB,KAAKe,KAAK,0BACNO,MAAbD,UAAyBP,SAASO,UAAW,SAAMC,GAS1DC,YAAc,SAASvB,aAChBc,SAASd,KAAKe,KAAK,iBAAkB,KAW5CS,aAAe,SAASxB,aACTuB,YAAYvB,MArLV,MAsLAmB,cAAcnB,OAY/ByB,WAAa,SAASzB,UAClB0B,QAAU,QAEV1B,KAAKe,KAAK,uBAEVW,QAAUC,KAAKC,MAAMC,KAAKC,MAAQ,SAC/B,OACGC,SAAWR,YAAYvB,MACvBqB,UAAYD,aAAapB,MAEdsB,MAAbD,YACAK,QAAUK,SA7MD,MA6MaV,kBAIvBK,SA4DPM,uBAAyB,SAAShC,KAAMiC,aAtBnB,SAASjC,aACvBA,KAAKkC,KAAK,wBAsBVC,CAAmBnC,MAAQiC,MAYlCG,qBAAuB,SAASC,QAASC,UAAWZ,QAASa,oBA3DrC,SAASC,UAAWF,UAAWG,MAAOf,QAASa,iBACnEG,KAAO,CACPC,UAAWH,UACXI,UAAWN,UACXG,MAAOA,cAGPf,UACAgB,KAAKG,QAAUnB,SAGfa,cACAG,KAAKI,YAAcP,aAGhB9C,iBAAiBsD,eAAeL,MAiDhCM,CAJSX,QAAQY,KAAI,SAASC,eAC1BA,OAAOC,MAGsBb,UAAWc,EAAwB1B,QAASa,cAcpFc,yBAA2B,SAAShB,QAASrC,KAAM+B,SAAUuB,WAAYjC,UAAWkC,eAE7EjE,UAAUkE,OAAO5D,uBAAwB,CAC5CyC,QAASA,QACTN,SAAUA,SACV0B,eAAe,EACfC,aAA2BpC,MAAbD,UACdsC,WAAYL,WACZM,UAAWvC,UACXwC,YAA0BvC,MAAbD,UACbvB,YAAY,EACZgE,YAAY,IACbC,MAAK,SAASC,aACbjE,uBAAuBC,MAEnBgE,MAzNgB,SAAShE,KAAMgE,UAAMT,mEACzC5C,UAAYX,KAAKC,KAAKN,wBAEtB4D,OACAjE,UAAU2E,mBAAmBtD,UAAWqD,KAAM,IAE9C1E,UAAUsB,oBAAoBD,UAAWqD,KAAM,IAsN3CE,CAAsBlE,KAAMgE,KAAMT,QAG/BS,QAEVD,MAAK,SAASC,aACP3B,QAAQ8B,OAtUD,EAyUPhE,sBAAsBH,MAGtBI,sBAAsBJ,MAGnBgE,QAEVI,OAAM,WACHrE,uBAAuBC,UAY3BqE,gBAAkB,SAASrE,UAAMuD,qEAC3Be,eAAiB,IAAI5E,QAAQ,wCAC/BuB,OAASJ,UAAUb,MACnByC,MAAQvB,SAASlB,YACfsC,UAAYd,aAAaxB,MACzB0B,QAAUD,WAAWzB,MACrBuC,YAAcvC,KAAKuE,QAAQ5E,0BAA0BM,KAAKN,2BAA2B6E,aAKpFhF,iBAAiBiF,qDA5WA,MA8WpBhC,MACAxB,OA9WU,eAgXVsB,YACAD,UACAZ,SACFqC,MAAK,SAASW,YACRC,sBAAwB9C,KAAKC,MAC7BO,QAAUqC,OAAOrC,QACjBuC,WAAaF,OAAOG,WACpBvB,WAAanC,cAAcnB,MAC3BqB,UAAYD,aAAapB,MACzB+B,SAAWR,YAAYvB,YACrB8E,qBAAuBJ,OAAOK,qBAGpC/D,UAAUhB,KAAM4E,gBAEZI,cAAgB5C,qBAAqBC,QAASC,UAAWZ,QAASa,aAElE0C,cAAgB5B,yBAAyBhB,QAASrC,KAAM+B,SAAUuB,WAAYjC,UAAWkC,eAEtFpE,EAAE+F,KAAKF,cAAeC,eACxBlB,MAAK,SAASoB,uBACPnD,uBAAuBhC,KAAM2E,yBAK7BtC,QAAQ8B,OAAS,GAEjB9B,QAAQ+C,SAAQ,SAASlC,cAEfmC,kBAAoB,2DADTnC,OAAOC,GAC0E,KAE5FmC,cADwBtF,KAAKC,KAAKoF,mBACIpF,KAAKV,UAAUgG,cAE3DhG,UAAUiG,KAAKF,cAAezF,qBAG7BiF,qBAKD1E,sBAAsBJ,MAHtBG,sBAAsBH,QAO1BG,sBAAsBH,MAGR,GAAViB,QACAP,+BAA+BV,QA3B5BmF,qBAiCpBpB,MAAK,IACGO,eAAemB,YACvBrB,MAAMhF,aAAasG,YAQtBC,uBAAyB,SAAS3F,MAClCX,aAAaH,OAAOc,KAAM,CAACX,aAAauG,OAAOC,WAE/C7F,KAAK8F,GAAGzG,aAAauG,OAAOC,SAAUlG,+BAA+B,SAASoG,EAAG7D,OArYhD,SAASlC,UACtCO,OAASP,KAAKC,KAAKN,+BACvBY,OAAOC,KAAK,YAAY,GACxBlB,UAAUkE,OAAO5D,uBAAwB,IACpCmE,MAAK,SAASC,aACXzD,OAAOgD,OAAOS,MACPA,QAEVI,OAAM,kBAEI,KA4XX4B,CAA+BhG,MAC/BqE,gBAAgBrE,MAAM,GACjB+D,MAAK,WACFzD,gCAAgCN,SAGnCoE,OAAM,WACH9D,gCAAgCN,SAGpCkC,OACAA,KAAK+D,cAAcC,iBACnBhE,KAAK+D,cAAcE,mBAEvBJ,EAAEI,sBAqDNC,MAAQ,SAASpG,MACZA,KAAKe,KAAK,cAAiBf,KAAKC,KAAKN,oCAAoCwE,SAC1EE,gBAAgBrE,MAChBA,KAAKe,KAAK,aAAa,WAIxB,CACHyE,KAhDO,SAASxF,OAChBA,KAAOb,EAAEa,OAGCC,KAAKN,oCAAoCwE,UAjN9B,SAASnE,KAAMiC,MACpCjC,KAAKkC,KAAK,uBAAwBD,MAiN9BoE,CAAmBrG,KAAM6B,KAAKC,OAE1B9B,KAAKsG,SAAS,YAEdjC,gBAAgBrE,MAChBA,KAAKe,KAAK,aAAa,IAG3B4E,uBAAuB3F,QAoC3BuG,MA1BQ,SAASvG,MAEjBgB,UAAUhB,KAAM,GArdW,SAASA,MACpCA,KAAKC,KAAKN,4CAA4CU,YAAY,UAqdlEmG,CAAuBxG,MAjZY,SAASA,MAC5CA,KAAKC,KAAKN,0CAA0CO,SAAS,UAiZ7DuG,CAA+BzG,MAC/BA,KAAK0G,WAAW,aAEZ1G,KAAKsG,SAAS,WACdF,MAAMpG,OAmBVoG,MAAOA"}PK'9\q(timeline/amd/build/view_dates.min.js.mapnu[{"version":3,"file":"view_dates.min.js","sources":["../src/view_dates.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Manage the timeline dates view for the timeline block.\n *\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n[\n 'jquery',\n 'block_timeline/event_list',\n 'core/pubsub',\n 'core/paged_content_events'\n],\nfunction(\n $,\n EventList,\n PubSub,\n PagedContentEvents\n) {\n\n var SELECTORS = {\n EVENT_LIST_CONTAINER: '[data-region=\"event-list-container\"]',\n NO_COURSES_EMPTY_MESSAGE: '[data-region=\"no-courses-empty-message\"]',\n };\n\n /**\n * Setup the listeners for the timeline block\n *\n * @param {string} root view dates container\n * @param {string} namespace The namespace for the paged content\n */\n var registerEventListeners = function(root, namespace) {\n var event = namespace + PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT;\n PubSub.subscribe(event, function(limit) {\n $(root).data('limit', limit);\n });\n };\n\n /**\n * Initialise the event list and being loading the events.\n *\n * @param {object} root The root element for the timeline dates view.\n */\n var load = function(root) {\n\n if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n var eventListContainer = root.find(SELECTORS.EVENT_LIST_CONTAINER);\n var namespace = $(eventListContainer).attr('id') + \"user_block_timeline\" + Math.random();\n registerEventListeners(root, namespace);\n\n var config = {\n persistentLimitKey: \"block_timeline_user_limit_preference\",\n eventNamespace: namespace\n };\n EventList.init(eventListContainer, config);\n }\n };\n\n /**\n * Initialise the timeline dates view. Begin loading the events\n * if this view is active.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var init = function(root) {\n root = $(root);\n\n // Only need to handle events loading if the user is actively enrolled in a course and this view is active.\n if (root.hasClass('active') && !root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n load(root);\n root.attr('data-seen', true);\n }\n };\n\n /**\n * Reset the view back to it's initial state. If this view is active then\n * beging loading the events.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var reset = function(root) {\n root.removeAttr('data-seen');\n if (root.hasClass('active')) {\n load(root);\n root.attr('data-seen', true);\n }\n };\n\n /**\n * Load the events if this is the first time the view is displayed.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var shown = function(root) {\n if (!root.attr('data-seen')) {\n load(root);\n root.attr('data-seen', true);\n }\n };\n\n return {\n init: init,\n reset: reset,\n shown: shown\n };\n});\n"],"names":["define","$","EventList","PubSub","PagedContentEvents","SELECTORS","load","root","find","length","eventListContainer","namespace","attr","Math","random","event","SET_ITEMS_PER_PAGE_LIMIT","subscribe","limit","data","registerEventListeners","config","persistentLimitKey","eventNamespace","init","hasClass","reset","removeAttr","shown"],"mappings":";;;;;;AAsBAA,mCACA,CACI,SACA,4BACA,cACA,8BAEJ,SACIC,EACAC,UACAC,OACAC,wBAGIC,+BACsB,uCADtBA,mCAE0B,2CAqB1BC,KAAO,SAASC,UAEXA,KAAKC,KAAKH,oCAAoCI,OAAQ,KACnDC,mBAAqBH,KAAKC,KAAKH,gCAC/BM,UAAYV,EAAES,oBAAoBE,KAAK,MAAQ,sBAAwBC,KAAKC,UAhB3D,SAASP,KAAMI,eACpCI,MAAQJ,UAAYP,mBAAmBY,yBAC3Cb,OAAOc,UAAUF,OAAO,SAASG,OAC7BjB,EAAEM,MAAMY,KAAK,QAASD,UActBE,CAAuBb,KAAMI,eAEzBU,OAAS,CACTC,mBAAoB,uCACpBC,eAAgBZ,WAEpBT,UAAUsB,KAAKd,mBAAoBW,gBA8CpC,CACHG,KArCO,SAASjB,OAChBA,KAAON,EAAEM,OAGAkB,SAAS,YAAclB,KAAKC,KAAKH,oCAAoCI,SAC1EH,KAAKC,MACLA,KAAKK,KAAK,aAAa,KAgC3Bc,MAtBQ,SAASnB,MACjBA,KAAKoB,WAAW,aACZpB,KAAKkB,SAAS,YACdnB,KAAKC,MACLA,KAAKK,KAAK,aAAa,KAmB3BgB,MAVQ,SAASrB,MACZA,KAAKK,KAAK,eACXN,KAAKC,MACLA,KAAKK,KAAK,aAAa"}PK'9\h`V"timeline/amd/build/main.min.js.mapnu[{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to initialise the timeline block.\n *\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n[\n 'jquery',\n 'block_timeline/view_nav',\n 'block_timeline/view'\n],\nfunction(\n $,\n ViewNav,\n View\n) {\n\n var SELECTORS = {\n TIMELINE_VIEW: '[data-region=\"timeline-view\"]'\n };\n\n /**\n * Initialise all of the modules for the timeline block.\n *\n * @param {object} root The root element for the timeline block.\n */\n var init = function(root) {\n root = $(root);\n var viewRoot = root.find(SELECTORS.TIMELINE_VIEW);\n\n // Initialise the timeline navigation elements.\n ViewNav.init(root, viewRoot);\n // Initialise the timeline view modules.\n View.init(viewRoot);\n };\n\n return {\n init: init\n };\n});\n"],"names":["define","$","ViewNav","View","SELECTORS","init","root","viewRoot","find"],"mappings":";;;;;;AAsBAA,6BACA,CACI,SACA,0BACA,wBAEJ,SACIC,EACAC,QACAC,UAGIC,wBACe,sCAkBZ,CACHC,KAXO,SAASC,UAEZC,UADJD,KAAOL,EAAEK,OACWE,KAAKJ,yBAGzBF,QAAQG,KAAKC,KAAMC,UAEnBJ,KAAKE,KAAKE"}PK'9\0R8timeline/amd/build/calendar_events_repository.min.js.mapnu[{"version":3,"file":"calendar_events_repository.min.js","sources":["../src/calendar_events_repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to retrieve calendar events from the server.\n *\n * @module block_timeline/calendar_events_repository\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) {\n\n var DEFAULT_LIMIT = 20;\n\n /**\n * Retrieve a list of calendar events for the logged in user for the\n * given course.\n *\n * Valid args are:\n * int courseid Only get events for this course\n * int starttime Only get events after this time\n * int endtime Only get events before this time\n * int limit Limit the number of results returned\n * int aftereventid Offset the result set from the given id\n *\n * @method queryByCourse\n * @param {object} args The request arguments\n * @return {promise} Resolved with an array of the calendar events\n */\n var queryByCourse = function(args) {\n if (!args.hasOwnProperty('limit')) {\n args.limit = DEFAULT_LIMIT;\n }\n\n args.limitnum = args.limit;\n delete args.limit;\n\n if (args.hasOwnProperty('starttime')) {\n args.timesortfrom = args.starttime;\n delete args.starttime;\n }\n\n if (args.hasOwnProperty('endtime')) {\n args.timesortto = args.endtime;\n delete args.endtime;\n }\n\n var request = {\n methodname: 'core_calendar_get_action_events_by_course',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n /**\n * Retrieve a list of calendar events for the given courses for the\n * logged in user.\n *\n * Valid args are:\n * array courseids Get events for these courses\n * int starttime Only get events after this time\n * int endtime Only get events before this time\n * int limit Limit the number of results returned\n *\n * @method queryByCourses\n * @param {object} args The request arguments\n * @return {promise} Resolved with an array of the calendar events\n */\n var queryByCourses = function(args) {\n if (!args.hasOwnProperty('limit')) {\n // This is intentionally smaller than the default limit.\n args.limit = 10;\n }\n\n args.limitnum = args.limit;\n delete args.limit;\n\n if (args.hasOwnProperty('starttime')) {\n args.timesortfrom = args.starttime;\n delete args.starttime;\n }\n\n if (args.hasOwnProperty('endtime')) {\n args.timesortto = args.endtime;\n delete args.endtime;\n }\n\n var request = {\n methodname: 'core_calendar_get_action_events_by_courses',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n /**\n * Retrieve a list of calendar events for the logged in user after the given\n * time.\n *\n * Valid args are:\n * int starttime Only get events after this time\n * int endtime Only get events before this time\n * int limit Limit the number of results returned\n * int aftereventid Offset the result set from the given id\n *\n * @method queryByTime\n * @param {object} args The request arguments\n * @return {promise} Resolved with an array of the calendar events\n */\n var queryByTime = function(args) {\n if (!args.hasOwnProperty('limit')) {\n args.limit = DEFAULT_LIMIT;\n }\n\n args.limitnum = args.limit;\n delete args.limit;\n\n if (args.hasOwnProperty('starttime')) {\n args.timesortfrom = args.starttime;\n delete args.starttime;\n }\n\n if (args.hasOwnProperty('endtime')) {\n args.timesortto = args.endtime;\n delete args.endtime;\n }\n // Don't show events related to courses that the user is suspended in.\n args.limittononsuspendedevents = true;\n\n var request = {\n methodname: 'core_calendar_get_action_events_by_timesort',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n return {\n queryByTime: queryByTime,\n queryByCourse: queryByCourse,\n queryByCourses: queryByCourses,\n };\n});\n"],"names":["define","$","Ajax","Notification","queryByTime","args","hasOwnProperty","limit","limitnum","timesortfrom","starttime","timesortto","endtime","limittononsuspendedevents","request","methodname","promise","call","fail","exception","queryByCourse","queryByCourses"],"mappings":";;;;;;;AAsBAA,mDAAO,CAAC,SAAU,YAAa,sBAAsB,SAASC,EAAGC,KAAMC,oBA4I5D,CACHC,YAjCc,SAASC,MAClBA,KAAKC,eAAe,WACrBD,KAAKE,MA5GO,IA+GhBF,KAAKG,SAAWH,KAAKE,aACdF,KAAKE,MAERF,KAAKC,eAAe,eACpBD,KAAKI,aAAeJ,KAAKK,iBAClBL,KAAKK,WAGZL,KAAKC,eAAe,aACpBD,KAAKM,WAAaN,KAAKO,eAChBP,KAAKO,SAGhBP,KAAKQ,2BAA4B,MAE7BC,QAAU,CACVC,WAAY,8CACZV,KAAMA,MAGNW,QAAUd,KAAKe,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKf,aAAagB,WAEnBH,SAKPI,cA3HgB,SAASf,MACpBA,KAAKC,eAAe,WACrBD,KAAKE,MAnBO,IAsBhBF,KAAKG,SAAWH,KAAKE,aACdF,KAAKE,MAERF,KAAKC,eAAe,eACpBD,KAAKI,aAAeJ,KAAKK,iBAClBL,KAAKK,WAGZL,KAAKC,eAAe,aACpBD,KAAKM,WAAaN,KAAKO,eAChBP,KAAKO,aAGZE,QAAU,CACVC,WAAY,4CACZV,KAAMA,MAGNW,QAAUd,KAAKe,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKf,aAAagB,WAEnBH,SAiGPK,eAhFiB,SAAShB,MACrBA,KAAKC,eAAe,WAErBD,KAAKE,MAAQ,IAGjBF,KAAKG,SAAWH,KAAKE,aACdF,KAAKE,MAERF,KAAKC,eAAe,eACpBD,KAAKI,aAAeJ,KAAKK,iBAClBL,KAAKK,WAGZL,KAAKC,eAAe,aACpBD,KAAKM,WAAaN,KAAKO,eAChBP,KAAKO,aAGZE,QAAU,CACVC,WAAY,6CACZV,KAAMA,MAGNW,QAAUd,KAAKe,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKf,aAAagB,WAEnBH"}PK'9\&"timeline/amd/build/view_nav.min.jsnu[define("block_timeline/view_nav",["exports","jquery","core/custom_interaction_events","block_timeline/view","core/notification","core/utils","core_user/repository","core/normalise"],(function(_exports,_jquery,CustomEvents,View,Notification,Utils,UserRepository,_normalise){var obj; /** * Manage the timeline view navigation for the timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj},CustomEvents=_interopRequireWildcard(CustomEvents),View=_interopRequireWildcard(View),Notification=_interopRequireWildcard(Notification),Utils=_interopRequireWildcard(Utils),UserRepository=_interopRequireWildcard(UserRepository);const SELECTORS_TIMELINE_DAY_FILTER='[data-region="day-filter"]',SELECTORS_TIMELINE_DAY_FILTER_OPTION="[data-from]",SELECTORS_TIMELINE_VIEW_SELECTOR='[data-region="view-selector"]',SELECTORS_DATA_DAYS_OFFSET="[data-days-offset]",SELECTORS_TIMELINE_SEARCH_INPUT='[data-action="search"]',SELECTORS_TIMELINE_SEARCH_CLEAR_ICON='[data-action="clearsearch"]',SELECTORS_NO_COURSES_EMPTY_MESSAGE='[data-region="no-courses-empty-message"]',activeSearchState=(clearSearchIcon,timelineViewRoot)=>{clearSearchIcon.removeClass("d-none"),View.reset(timelineViewRoot)},clearSearchState=(clearSearchIcon,timelineViewRoot)=>{clearSearchIcon.addClass("d-none"),View.reset(timelineViewRoot)};_exports.init=function(root,timelineViewRoot){(function(root,timelineViewRoot){const viewSelector=root.find(SELECTORS_TIMELINE_VIEW_SELECTOR);(0,_normalise.getFirst)(viewSelector).querySelectorAll('[data-bs-toggle="tab"]').forEach((tab=>{tab.addEventListener("shown.bs.tab",(e=>{View.shown(timelineViewRoot),(0,_jquery.default)(e.target).removeClass("active")}))})),CustomEvents.define(viewSelector,[CustomEvents.events.activate]),viewSelector.on(CustomEvents.events.activate,"[data-bs-toggle='tab']",(function(e){var filtername=(0,_jquery.default)(e.currentTarget).data("filtername");UserRepository.setUserPreference("block_timeline_user_sort_preference",filtername).catch(Notification.exception)}))})(root=(0,_jquery.default)(root),timelineViewRoot),root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length||(function(root,timelineViewRoot){const timelineDaySelectorContainer=root.find(SELECTORS_TIMELINE_DAY_FILTER);CustomEvents.define(timelineDaySelectorContainer,[CustomEvents.events.activate]),timelineDaySelectorContainer.on(CustomEvents.events.activate,SELECTORS_TIMELINE_DAY_FILTER_OPTION,(function(e,data){var filtername=(0,_jquery.default)(e.currentTarget).data("filtername");UserRepository.setUserPreference("block_timeline_user_filter_preference",filtername).catch(Notification.exception);var option=(0,_jquery.default)(e.target).closest(SELECTORS_TIMELINE_DAY_FILTER_OPTION);if("true"!=option.attr("aria-current")){var daysOffset=option.attr("data-from"),daysLimit=option.attr("data-to"),elementsWithDaysOffset=root.find(SELECTORS_DATA_DAYS_OFFSET);elementsWithDaysOffset.attr("data-days-offset",daysOffset),null!=daysLimit?elementsWithDaysOffset.attr("data-days-limit",daysLimit):elementsWithDaysOffset.removeAttr("data-days-limit"),"overdue"===option.attr("data-filtername")?elementsWithDaysOffset.attr("data-filter-overdue",!0):elementsWithDaysOffset.removeAttr("data-filter-overdue"),View.reset(timelineViewRoot),data.originalEvent.preventDefault()}}))}(root,timelineViewRoot),((root,timelineViewRoot)=>{const searchInput=root.find(SELECTORS_TIMELINE_SEARCH_INPUT),clearSearchIcon=root.find(SELECTORS_TIMELINE_SEARCH_CLEAR_ICON);searchInput.on("input",Utils.debounce((()=>{""!==searchInput.val()?activeSearchState(clearSearchIcon,timelineViewRoot):clearSearchState(clearSearchIcon,timelineViewRoot)}),1e3)),clearSearchIcon.on("click",(()=>{searchInput.val(""),clearSearchState(clearSearchIcon,timelineViewRoot),searchInput.focus()}))})(root,timelineViewRoot))}})); //# sourceMappingURL=view_nav.min.js.mapPK'9\h>&timeline/amd/build/view_courses.min.jsnu[/** * Manage the timeline courses view for the timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_timeline/view_courses",["jquery","core/notification","core/custom_interaction_events","core/templates","block_timeline/event_list","core_course/repository","block_timeline/calendar_events_repository","core/pending"],(function($,Notification,CustomEvents,Templates,EventList,CourseRepository,EventsRepository,Pending){var SELECTORS_MORE_COURSES_BUTTON='[data-action="more-courses"]',SELECTORS_MORE_COURSES_BUTTON_CONTAINER='[data-region="more-courses-button-container"]',SELECTORS_NO_COURSES_EMPTY_MESSAGE='[data-region="no-courses-empty-message"]',SELECTORS_NO_COURSES_WITH_EVENTS_MESSAGE='[data-region="no-events-empty-message"]',SELECTORS_COURSES_LIST='[data-region="courses-list"]',SELECTORS_COURSE_ITEMS_LOADING_PLACEHOLDER='[data-region="course-items-loading-placeholder"]',SELECTORS_LOADING_ICON=".loading-icon",SELECTORS_TIMELINE_BLOCK='[data-region="timeline"]',SELECTORS_TIMELINE_SEARCH='[data-action="search"]',TEMPLATES_COURSE_ITEMS="block_timeline/course-items",TEMPLATES_LOADING_ICON="core/loading";const additionalConfig={courseview:!0};var hideLoadingPlaceholder=function(root){root.find(SELECTORS_COURSE_ITEMS_LOADING_PLACEHOLDER).addClass("hidden")};var hideMoreCoursesButton=function(root){root.find(SELECTORS_MORE_COURSES_BUTTON_CONTAINER).addClass("hidden")},showMoreCoursesButton=function(root){root.find(SELECTORS_MORE_COURSES_BUTTON_CONTAINER).removeClass("hidden")},disableMoreCoursesButtonLoading=function(root){var button=root.find(SELECTORS_MORE_COURSES_BUTTON);button.prop("disabled",!1),button.find(SELECTORS_LOADING_ICON).remove()};const showNoCoursesWithEventsMessage=function(root){const container=root.find(SELECTORS_COURSES_LIST);Templates.replaceNodeContents(container,"",""),root.find(SELECTORS_NO_COURSES_WITH_EVENTS_MESSAGE).removeClass("hidden")};var getOffset=function(root){return parseInt(root.attr("data-offset"),10)},setOffset=function(root,offset){root.attr("data-offset",offset)},getLimit=function(root){return parseInt(root.attr("data-limit"),10)},getDaysOffset=function(root){return parseInt(root.attr("data-days-offset"),10)},getDaysLimit=function(root){var daysLimit=root.attr("data-days-limit");return null!=daysLimit?parseInt(daysLimit,10):void 0},getMidnight=function(root){return parseInt(root.attr("data-midnight"),10)},getStartTime=function(root){return getMidnight(root)+86400*getDaysOffset(root)},getEndTime=function(root){let endTime=null;if(root.attr("data-filter-overdue"))endTime=Math.floor(Date.now()/1e3);else{const midnight=getMidnight(root),daysLimit=getDaysLimit(root);null!=daysLimit&&(endTime=midnight+86400*daysLimit)}return endTime},hasReloadedEventsSince=function(root,time){return function(root){return root.data("last-event-load-time")}(root)>time},loadEventsForCourses=function(courses,startTime,endTime,searchValue){return function(courseIds,startTime,limit,endTime,searchValue){var args={courseids:courseIds,starttime:startTime,limit:limit};return endTime&&(args.endtime=endTime),searchValue&&(args.searchvalue=searchValue),EventsRepository.queryByCourses(args)}(courses.map((function(course){return course.id})),startTime,6,endTime,searchValue)},updateDisplayFromCourses=function(courses,root,midnight,daysOffset,daysLimit,append){return Templates.render(TEMPLATES_COURSE_ITEMS,{courses:courses,midnight:midnight,hasdaysoffset:!0,hasdayslimit:null!=daysLimit,daysoffset:daysOffset,dayslimit:daysLimit,nodayslimit:null==daysLimit,courseview:!0,hascourses:!0}).then((function(html){return hideLoadingPlaceholder(root),html&&function(root,html){let append=arguments.length>2&&void 0!==arguments[2]&&arguments[2];var container=root.find(SELECTORS_COURSES_LIST);append?Templates.appendNodeContents(container,html,""):Templates.replaceNodeContents(container,html,"")}(root,html,append),html})).then((function(html){return courses.length<2?hideMoreCoursesButton(root):showMoreCoursesButton(root),html})).catch((function(){hideLoadingPlaceholder(root)}))},loadMoreCourses=function(root){let append=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const pendingPromise=new Pending("block/timeline:load-more-courses");var offset=getOffset(root),limit=getLimit(root);const startTime=getStartTime(root),endTime=getEndTime(root),searchValue=root.closest(SELECTORS_TIMELINE_BLOCK).find(SELECTORS_TIMELINE_SEARCH).val();return CourseRepository.getEnrolledCoursesWithEventsByTimelineClassification("all",limit,offset,"fullname asc",searchValue,startTime,endTime).then((function(result){var startEventLoadingTime=Date.now(),courses=result.courses,nextOffset=result.nextoffset,daysOffset=getDaysOffset(root),daysLimit=getDaysLimit(root),midnight=getMidnight(root);const moreCoursesAvailable=result.morecoursesavailable;setOffset(root,nextOffset);var eventsPromise=loadEventsForCourses(courses,startTime,endTime,searchValue),renderPromise=updateDisplayFromCourses(courses,root,midnight,daysOffset,daysLimit,append);return $.when(eventsPromise,renderPromise).then((function(eventsByCourse){return hasReloadedEventsSince(root,startEventLoadingTime)||(courses.length>0?(courses.forEach((function(course){const containerSelector='[data-region="course-events-container"][data-course-id="'+course.id+'"]',eventListRoot=root.find(containerSelector).find(EventList.rootSelector);EventList.init(eventListRoot,additionalConfig)})),moreCoursesAvailable?showMoreCoursesButton(root):hideMoreCoursesButton(root)):(hideMoreCoursesButton(root),0==offset&&showNoCoursesWithEventsMessage(root))),eventsByCourse}))})).then((()=>pendingPromise.resolve())).catch(Notification.exception)},registerEventListeners=function(root){CustomEvents.define(root,[CustomEvents.events.activate]),root.on(CustomEvents.events.activate,SELECTORS_MORE_COURSES_BUTTON,(function(e,data){!function(root){var button=root.find(SELECTORS_MORE_COURSES_BUTTON);button.prop("disabled",!0),Templates.render(TEMPLATES_LOADING_ICON,{}).then((function(html){return button.append(html),html})).catch((function(){return!1}))}(root),loadMoreCourses(root,!0).then((function(){disableMoreCoursesButtonLoading(root)})).catch((function(){disableMoreCoursesButtonLoading(root)})),data&&(data.originalEvent.preventDefault(),data.originalEvent.stopPropagation()),e.stopPropagation()}))},shown=function(root){root.attr("data-seen")||root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length||(loadMoreCourses(root),root.attr("data-seen",!0))};return{init:function(root){(root=$(root)).find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length||(!function(root,time){root.data("last-event-load-time",time)}(root,Date.now()),root.hasClass("active")&&(loadMoreCourses(root),root.attr("data-seen",!0)),registerEventListeners(root))},reset:function(root){setOffset(root,0),function(root){root.find(SELECTORS_COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass("hidden")}(root),function(root){root.find(SELECTORS_NO_COURSES_WITH_EVENTS_MESSAGE).addClass("hidden")}(root),root.removeAttr("data-seen"),root.hasClass("active")&&shown(root)},shown:shown}})); //# sourceMappingURL=view_courses.min.js.mapPK'9\5x&&"timeline/amd/build/view.min.js.mapnu[{"version":3,"file":"view.min.js","sources":["../src/view.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Manage the timeline view for the timeline block.\n *\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n[\n 'jquery',\n 'block_timeline/view_dates',\n 'block_timeline/view_courses',\n],\nfunction(\n $,\n ViewDates,\n ViewCourses\n) {\n\n var SELECTORS = {\n TIMELINE_DATES_VIEW: '[data-region=\"view-dates\"]',\n TIMELINE_COURSES_VIEW: '[data-region=\"view-courses\"]',\n };\n\n /**\n * Intialise the timeline dates and courses views on page load.\n * This function should only be called once per page load because\n * it can cause event listeners to be added to the page.\n *\n * @param {object} root The root element for the timeline view.\n */\n var init = function(root) {\n root = $(root);\n var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW);\n var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW);\n\n ViewDates.init(datesViewRoot);\n ViewCourses.init(coursesViewRoot);\n };\n\n /**\n * Reset the timeline dates and courses views to their original\n * state on first page load.\n *\n * This is called when configuration has changed for the event lists\n * to cause them to reload their data.\n *\n * @param {object} root The root element for the timeline view.\n */\n var reset = function(root) {\n var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW);\n var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW);\n ViewDates.reset(datesViewRoot);\n ViewCourses.reset(coursesViewRoot);\n };\n\n /**\n * Tell the timeline dates or courses view that it has been displayed.\n *\n * This is called each time one of the views is displayed and is used to\n * lazy load the data within it on first load.\n *\n * @param {object} root The root element for the timeline view.\n */\n var shown = function(root) {\n var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW);\n var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW);\n\n if (datesViewRoot.hasClass('active')) {\n ViewDates.shown(datesViewRoot);\n } else {\n ViewCourses.shown(coursesViewRoot);\n }\n };\n\n return {\n init: init,\n reset: reset,\n shown: shown,\n };\n});\n"],"names":["define","$","ViewDates","ViewCourses","SELECTORS","init","root","datesViewRoot","find","coursesViewRoot","reset","shown","hasClass"],"mappings":";;;;;;AAsBAA,6BACA,CACI,SACA,4BACA,gCAEJ,SACIC,EACAC,UACAC,iBAGIC,8BACqB,6BADrBA,gCAEuB,qCAsDpB,CACHC,KA7CO,SAASC,UAEZC,eADJD,KAAOL,EAAEK,OACgBE,KAAKJ,+BAC1BK,gBAAkBH,KAAKE,KAAKJ,iCAEhCF,UAAUG,KAAKE,eACfJ,YAAYE,KAAKI,kBAwCjBC,MA5BQ,SAASJ,UACbC,cAAgBD,KAAKE,KAAKJ,+BAC1BK,gBAAkBH,KAAKE,KAAKJ,iCAChCF,UAAUQ,MAAMH,eAChBJ,YAAYO,MAAMD,kBAyBlBE,MAdQ,SAASL,UACbC,cAAgBD,KAAKE,KAAKJ,+BAC1BK,gBAAkBH,KAAKE,KAAKJ,iCAE5BG,cAAcK,SAAS,UACvBV,UAAUS,MAAMJ,eAEhBJ,YAAYQ,MAAMF"}PK'9\timeline/amd/build/view.min.jsnu[/** * Manage the timeline view for the timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_timeline/view",["jquery","block_timeline/view_dates","block_timeline/view_courses"],(function($,ViewDates,ViewCourses){var SELECTORS_TIMELINE_DATES_VIEW='[data-region="view-dates"]',SELECTORS_TIMELINE_COURSES_VIEW='[data-region="view-courses"]';return{init:function(root){var datesViewRoot=(root=$(root)).find(SELECTORS_TIMELINE_DATES_VIEW),coursesViewRoot=root.find(SELECTORS_TIMELINE_COURSES_VIEW);ViewDates.init(datesViewRoot),ViewCourses.init(coursesViewRoot)},reset:function(root){var datesViewRoot=root.find(SELECTORS_TIMELINE_DATES_VIEW),coursesViewRoot=root.find(SELECTORS_TIMELINE_COURSES_VIEW);ViewDates.reset(datesViewRoot),ViewCourses.reset(coursesViewRoot)},shown:function(root){var datesViewRoot=root.find(SELECTORS_TIMELINE_DATES_VIEW),coursesViewRoot=root.find(SELECTORS_TIMELINE_COURSES_VIEW);datesViewRoot.hasClass("active")?ViewDates.shown(datesViewRoot):ViewCourses.shown(coursesViewRoot)}}})); //# sourceMappingURL=view.min.js.mapPK'9\ӼQtimeline/amd/build/main.min.jsnu[/** * Javascript to initialise the timeline block. * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_timeline/main",["jquery","block_timeline/view_nav","block_timeline/view"],(function($,ViewNav,View){var SELECTORS_TIMELINE_VIEW='[data-region="timeline-view"]';return{init:function(root){var viewRoot=(root=$(root)).find(SELECTORS_TIMELINE_VIEW);ViewNav.init(root,viewRoot),View.init(viewRoot)}}})); //# sourceMappingURL=main.min.js.mapPK'9\*a3< < (timeline/tests/privacy/provider_test.phpnu[. /** * Unit tests for the block_timeline implementation of the privacy API. * * @package block_timeline * @category test * @copyright 2018 Peter Dias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_timeline\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\local\request\writer; use block_timeline\privacy\provider; /** * Unit tests for the block_timeline implementation of the privacy API. * * @copyright 2018 Peter Dias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends \core_privacy\tests\provider_testcase { /** * Ensure that export_user_preferences returns no data if the user has not visited the myoverview block. */ public function test_export_user_preferences_no_pref(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); provider::export_user_preferences($user->id); $writer = writer::with_context(\context_system::instance()); $this->assertFalse($writer->has_any_data()); } /** * Test the export_user_preferences given different inputs * * @param string $type The name of the user preference to get/set * @param string $value The value you are storing * @param string $expected The expected value override * * @dataProvider user_preference_provider */ public function test_export_user_preferences($type, $value, $expected): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); set_user_preference($type, $value, $user); provider::export_user_preferences($user->id); $writer = writer::with_context(\context_system::instance()); $blockpreferences = $writer->get_user_preferences('block_timeline'); if (!$expected) { $expected = get_string($value, 'block_timeline'); } $this->assertEquals($expected, $blockpreferences->{$type}->value); } /** * Create an array of valid user preferences for the timeline block. * * @return array Array of valid user preferences. */ public static function user_preference_provider(): array { return array( array('block_timeline_user_sort_preference', 'sortbydates', ''), array('block_timeline_user_sort_preference', 'sortbycourses', ''), array('block_timeline_user_sort_preference', 'next7days', ''), array('block_timeline_user_sort_preference', 'all', ''), array('block_timeline_user_limit_preference', 5, 5), ); } } PK'9\yaB((1timeline/tests/behat/block_timeline_dates.featurenu[@block @block_timeline @javascript Feature: The timeline block allows users to see upcoming activities In order to enable the timeline block As a student I can add the timeline block to my dashboard Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | And the following "courses" exist: | fullname | shortname | category | startdate | enddate | | Course 1 | C1 | 0 | ##1 month ago## | ##15 days ago## | | Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## | | Course 3 | C3 | 0 | ##first day of next month## | ##last day of next month## | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | timeclose | | choice | C2 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## | | choice | C1 | choice2 | Test choice 2 | Test choice description | ##1 month ago## | ##15 days ago## | | choice | C3 | choice3 | Test choice 3 | Test choice description | ##first day of +5 months## | ##last day of +5 months## | | feedback | C2 | feedback1 | Test feedback 1 | Test feedback description | ##yesterday## | ##tomorrow## | | feedback | C1 | feedback2 | Test feedback 2 | Test feedback description | ##first day of +10 months## | ##last day of +10 months## | | feedback | C3 | feedback3 | Test feedback 3 | Test feedback description | ##first day of +5 months## | ##last day of +5 months## | | feedback | C2 | feedback4 | Test feedback 4 | Test feedback description | ##yesterday## | ##now +1 minute## | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | duedate | | assign | C1 | assign1 | Test assign 1 | Test assign description | ##1 month ago## | ##yesterday## | | assign | C2 | assign2 | Test assign 2 | Test assign description | ##yesterday## | ##now -1 minute## | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | And I change window size to "large" Scenario: Next 7 days in date view Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" When I click on "Next 7 days" "link" in the "Timeline" "block" Then "Test choice 1" "link" should exist in the "Timeline" "block" And I should see "Choice closes · Course 2" in the "Timeline" "block" And "Test feedback 1" "link" should exist in the "Timeline" "block" And I should see "Feedback closes · Course 2" in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And "Test feedback 4" "link" should exist in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" And "Test choice 3" "link" should not exist in the "Timeline" "block" And "Test feedback 3" "link" should not exist in the "Timeline" "block" And "Test assign 1" "link" should not exist in the "Timeline" "block" And I should not see "Assignment is due · Course 1" in the "Timeline" "block" Scenario: Overdue in date view Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" When I click on "Overdue" "link" in the "Timeline" "block" Then "Test assign 1" "link" should exist in the "Timeline" "block" And I should see "Assignment is due · Course 1" in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" And "Test feedback 1" "link" should not exist in the "Timeline" "block" And "Test choice 1" "link" should not exist in the "Timeline" "block" And "Test choice 3" "link" should not exist in the "Timeline" "block" And "Test feedback 3" "link" should not exist in the "Timeline" "block" And "Test feedback 4" "link" should not exist in the "Timeline" "block" Scenario: All in date view Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" When I click on "All" "link" in the "Timeline" "block" Then "Test assign 1" "link" should exist in the "Timeline" "block" And I should see "Assignment is due · Course 1" in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And I should see "Assignment is due · Course 2" in the "Timeline" "block" And "Test feedback 1" "link" should exist in the "Timeline" "block" And I should see "Feedback closes · Course 2" in the "Timeline" "block" And "Test choice 1" "link" should exist in the "Timeline" "block" And I should see "Choice closes · Course 2" in the "Timeline" "block" And "Test feedback 4" "link" should exist in the "Timeline" "block" And I should see "Feedback closes · Course 2" in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" And "Test feedback 2" "link" should not exist in the "Timeline" "block" And I click on "Show more activities" "button" And "Test feedback 2" "link" should exist in the "Timeline" "block" And I should see "Feedback closes · Course 1" in the "Timeline" "block" And "Test choice 3" "link" should exist in the "Timeline" "block" And I should see "Test assign 1" in the "Timeline" "block" And I should see "Test feedback 1" in the "Timeline" "block" And I should see "Test choice 1" in the "Timeline" "block" And I should see "Test choice 3" in the "Timeline" "block" And I should see "Test feedback 3" in the "Timeline" "block" And I should not see "Test choice 2" in the "Timeline" "block" Scenario: Persistent All in date view Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" When I click on "All" "link" in the "Timeline" "block" And I reload the page Then "Test assign 1" "link" should exist in the "Timeline" "block" And I should see "Assignment is due · Course 1" in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And I should see "Assignment is due · Course 2" in the "Timeline" "block" And "Test feedback 1" "link" should exist in the "Timeline" "block" And I should see "Feedback closes · Course 2" in the "Timeline" "block" And "Test choice 1" "link" should exist in the "Timeline" "block" And I should see "Choice closes · Course 2" in the "Timeline" "block" And "Test feedback 4" "link" should exist in the "Timeline" "block" And I should see "Feedback closes · Course 2" in the "Timeline" "block" And I should not see "Test choice 2" in the "Timeline" "block" And I should not see "Test feedback 2" in the "Timeline" "block" And I click on "Show more activities" "button" And "Test feedback 2" "link" should exist in the "Timeline" "block" And I should see "Feedback closes · Course 1" in the "Timeline" "block" And I should see "Test assign 1" in the "Timeline" "block" And I should see "Test feedback 1" in the "Timeline" "block" And I should see "Test feedback 3" in the "Timeline" "block" And I should see "Test choice 1" in the "Timeline" "block" And I should not see "Test choice 2" in the "Timeline" "block" And I should see "Test choice 3" in the "Timeline" "block" Scenario: Persistent Overdue in date view Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" When I click on "Overdue" "link" in the "Timeline" "block" And I reload the page Then "Test assign 1" "link" should exist in the "Timeline" "block" And I should see "Assignment is due · Course 1" in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And I should see "Assignment is due · Course 2" in the "Timeline" "block" And "Test feedback 1" "link" should not exist in the "Timeline" "block" And "Test feedback 3" "link" should not exist in the "Timeline" "block" And "Test feedback 4" "link" should not exist in the "Timeline" "block" And "Test choice 1" "link" should not exist in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" And "Test choice 3" "link" should not exist in the "Timeline" "block" Scenario: Current filtering always applies in date view Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "Overdue" "link" in the "Timeline" "block" And I reload the page And "Test assign 1" "link" should exist in the "Timeline" "block" And "Test feedback 2" "link" should not exist in the "Timeline" "block" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" # Confirm that when we switch back to date view, the "All" filer continues to be applied (and not "overdue") When I click on "All" "link" in the "Timeline" "block" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by dates" "link" in the "Timeline" "block" Then "Test assign 1" "link" should exist in the "Timeline" "block" And I click on "Show more activities" "button" And "Test feedback 2" "link" should exist in the "Timeline" "block" Scenario: Student not enrolled in any courses sees a message Given I log in as "student2" When I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by dates" "link" in the "Timeline" "block" Then I should see "No in-progress courses" in the "Timeline" "block" And I should not see "Test choice 1" PK'9\A,8timeline/tests/behat/block_timeline_lazy_loading.featurenu[@block @block_timeline @javascript Feature: The timeline block allows users to use the lazy loading to view more activities Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | startdate | enddate | | Course 1 | C1 | 0 | ##yesterday## | ##last day of next month## | | Course 2 | C2 | 0 | ##yesterday## | ##last day of next month## | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | timeclose | | choice | C1 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## | | choice | C1 | choice2 | Test choice 2 | Test choice description | ##yesterday## | ##+1 days## | | choice | C1 | choice3 | Test choice 3 | Test choice description | ##yesterday## | ##+2 days## | | choice | C1 | choice4 | Test choice 4 | Test choice description | ##yesterday## | ##+3 days## | | choice | C1 | choice5 | Test choice 5 | Test choice description | ##yesterday## | ##+4 days## | | choice | C1 | choice6 | Test choice 6 | Test choice description | ##yesterday## | ##+5 days## | | choice | C1 | choice7 | Test choice 7 | Test choice description | ##yesterday## | ##+6 days## | | choice | C1 | choice8 | Test choice 8 | Test choice description | ##yesterday## | ##+7 days## | | choice | C1 | choice9 | Test choice 9 | Test choice description | ##yesterday## | ##+8 days## | | choice | C1 | choice10 | Test choice 10 | Test choice description | ##yesterday## | ##+9 days## | | choice | C2 | choice10 | Test choice 11 | Test choice description | ##yesterday## | ##+9 days## | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | duedate | | assign | C1 | assign1 | Test assign 1 | Test assign description | ##1 month ago## | ##yesterday## | Scenario: Lazy loading for date view Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" When I click on "All" "link" in the "Timeline" "block" Then I should see "Test choice 1" in the "Timeline" "block" And "Test assign 1" "link" should exist in the "Timeline" "block" And "Test choice 1" "link" should exist in the "Timeline" "block" And "Test choice 2" "link" should exist in the "Timeline" "block" And "Test choice 3" "link" should exist in the "Timeline" "block" And "Test choice 4" "link" should exist in the "Timeline" "block" And "Test choice 5" "link" should not exist in the "Timeline" "block" And "Test choice 6" "link" should not exist in the "Timeline" "block" And "Test choice 7" "link" should not exist in the "Timeline" "block" And "Test choice 8" "link" should not exist in the "Timeline" "block" And "Test choice 9" "link" should not exist in the "Timeline" "block" And "Test choice 10" "link" should not exist in the "Timeline" "block" And "Test choice 11" "link" should not exist in the "Timeline" "block" And I click on "Show more activities" "button" in the "[data-region='view-dates']" "css_element" And "Test choice 5" "link" should exist in the "Timeline" "block" And "Test choice 6" "link" should exist in the "Timeline" "block" And "Test choice 7" "link" should exist in the "Timeline" "block" And "Test choice 8" "link" should exist in the "Timeline" "block" And "Test choice 9" "link" should exist in the "Timeline" "block" And "Test choice 10" "link" should exist in the "Timeline" "block" And "Test choice 11" "link" should exist in the "Timeline" "block" Scenario: Lazy loading for course view Given I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" When I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" And "Test choice 1" "link" should exist in the "Timeline" "block" And "Test choice 2" "link" should exist in the "Timeline" "block" And "Test choice 3" "link" should exist in the "Timeline" "block" And "Test choice 4" "link" should exist in the "Timeline" "block" And "Test choice 5" "link" should exist in the "Timeline" "block" And "Test choice 11" "link" should exist in the "Timeline" "block" And "Test choice 6" "link" should not exist in the "Timeline" "block" And "Test choice 7" "link" should not exist in the "Timeline" "block" And "Test choice 8" "link" should not exist in the "Timeline" "block" And "Test choice 9" "link" should not exist in the "Timeline" "block" And "Test choice 10" "link" should not exist in the "Timeline" "block" And I click on "Show more activities" "button" in the "[data-region='view-courses']" "css_element" And "Test choice 6" "link" should exist in the "Timeline" "block" And "Test choice 7" "link" should exist in the "Timeline" "block" And "Test choice 8" "link" should exist in the "Timeline" "block" And "Test choice 9" "link" should exist in the "Timeline" "block" And "Test choice 10" "link" should exist in the "Timeline" "block" Scenario: The lazy loading button will not be shown if the number of events is smaller than 5 Given I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by dates" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" When I click on "Overdue" "link" in the "Timeline" "block" Then "Test assign 1" "link" should exist in the "Timeline" "block" And "Show more activities" "button" should not exist in the "[data-region='view-courses']" "css_element" PK'9\w!&!&;timeline/tests/behat/block_timeline_courses_overdue.featurenu[@block @block_timeline @javascript Feature: The timeline block allows users to see courses with overdue activities In order to view overdue activities in the timeline block As a student I can select the overdue filter in courses view Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | startdate | enddate | | Course 1 | C1 | 0 | ##now -3 months## | ##tomorrow## | | Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## | | Course 3 | C3 | 0 | ##yesterday## | ##tomorrow## | | Course 4 | C4 | 0 | ##yesterday## | ##tomorrow## | | Course 5 | C5 | 0 | ##yesterday## | ##tomorrow## | | Course 6 | C6 | 0 | ##yesterday## | ##tomorrow## | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | duedate | | assign | C1 | assign1 | Test assign 1 | Assign due last month | ##now -2 months## | ##now -1 month## | | assign | C2 | assign2 | Test assign 2 | Assign due yesterday | ##now -2 days## | ##yesterday## | | assign | C3 | assign3 | Test assign 3 | Assign due yesterday | ##now -2 days## | ##yesterday## | | assign | C4 | assign4 | Test assign 4 | Assign due later today | ##yesterday## | ##now +10 minutes## | | assign | C5 | assign5 | Test assign 5 | Assign due yesterday | ##now -2 days## | ##yesterday## | | assign | C6 | assign6 | Test assign 6 | Assign due tomorrow | ##yesterday## | ##tomorrow## | Scenario: No activities to display as overdue displays expected message Given the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C4 | student | | student1 | C6 | student | Given I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" When I click on "Overdue" "link" in the "Timeline" "block" Then I should see "No activities require action" in the "Timeline" "block" And I reload the page And I should see "No activities require action" in the "Timeline" "block" Scenario: If filtering by overdue, only courses with a matching item are included Given the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C4 | student | | student1 | C5 | student | | student1 | C6 | student | When I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "Overdue" "link" in the "Timeline" "block" Then I should not see "Show more courses" in the "Timeline" "block" And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 6" in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And I reload the page And I should not see "Show more courses" in the "Timeline" "block" And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 6" in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" Scenario: If filtering by overdue, only courses with a matching item are included and loading more is supported Given the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | | student1 | C6 | student | When I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "Overdue" "link" in the "Timeline" "block" And I click on "Show more courses" "button" in the "Timeline" "block" Then I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 6" in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Show more courses" in the "Timeline" "block" And I reload the page And I click on "Show more courses" "button" in the "Timeline" "block" And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 6" in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" PK'9\"{F{F3timeline/tests/behat/block_timeline_courses.featurenu[@block @block_timeline @javascript Feature: The timeline block allows users to see upcoming courses In order to enable the timeline block As a student I can add the timeline block to my dashboard Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | And the following "courses" exist: | fullname | shortname | category | startdate | enddate | | Course 1 | C1 | 0 | ##yesterday## | ##tomorrow## | | Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## | | Course 3 | C3 | 0 | ##yesterday## | ##tomorrow## | | Course 4 | C4 | 0 | ##first day of next month## | ##last day of next month## | | Course 5 | C5 | 0 | ##yesterday## | ##tomorrow## | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | timeclose | | choice | C2 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## | | choice | C1 | choice2 | Test choice 2 | Test choice description | ##1 month ago## | ##15 days ago## | | choice | C3 | choice3 | Test choice 3 | Test choice description | ##first day of +5 months## | ##last day of +5 months## | | feedback | C2 | feedback1 | Test feedback 1 | Test feedback description | ##yesterday## | ##tomorrow## | | feedback | C1 | feedback2 | Test feedback 2 | Test feedback description | ##first day of +10 months## | ##last day of +10 months## | | feedback | C3 | feedback3 | Test feedback 3 | Test feedback description | ##first day of +5 months## | ##last day of +5 months## | | feedback | C4 | feedback4 | Test feedback 4 | Test feedback description | ##yesterday## | ##tomorrow## | | feedback | C1 | feedback5 | Test feedback 5 | Test feedback description | ##yesterday## | ##now +1 minute## | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | duedate | | assign | C1 | assign1 | Test assign 1 | Test assign description | ##1 month ago## | ##yesterday## | | assign | C2 | assign2 | Test assign 2 | Test assign description | ##yesterday## | ##now -1 minute## | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | Scenario: Next 30 days in course view Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "Next 30 days" "link" in the "Timeline" "block" And I click on "Sort timeline items" "button" in the "Timeline" "block" When I click on "Sort by courses" "link" in the "Timeline" "block" Then I should see "Course 1" in the ".block_timeline [data-region='view-courses']" "css_element" And I should see "Course 2" in the ".block_timeline [data-region='view-courses']" "css_element" And I should see "Show more courses" in the "Timeline" "block" And I click on "Show more courses" "button" in the "Timeline" "block" And I should see "Course 4" in the ".block_timeline [data-region='view-courses']" "css_element" And "Test choice 1" "link" should exist in the "Timeline" "block" And I should see "Choice closes" in the "Timeline" "block" And "Test feedback 1" "link" should exist in the "Timeline" "block" And I should see "Feedback closes" in the "Timeline" "block" And "Test feedback 4" "link" should exist in the "Timeline" "block" And "Test feedback 5" "link" should exist in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And I should not see "Course 3" in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" And "Test choice 3" "link" should not exist in the "Timeline" "block" And "Test feedback 2" "link" should not exist in the "Timeline" "block" And "Test feedback 3" "link" should not exist in the "Timeline" "block" And "Test assign 1" "link" should not exist in the "Timeline" "block" Scenario: All in course view Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" When I click on "Show more courses" "button" in the "Timeline" "block" Then I should see "Course 3" in the "Timeline" "block" And I should see "Course 2" in the "Timeline" "block" And I should see "Course 1" in the "Timeline" "block" And "Test choice 1" "link" should exist in the "Timeline" "block" And "Test choice 3" "link" should exist in the "Timeline" "block" And "Test feedback 1" "link" should exist in the "Timeline" "block" And "Test feedback 2" "link" should exist in the "Timeline" "block" And "Test feedback 3" "link" should exist in the "Timeline" "block" And "Test feedback 5" "link" should exist in the "Timeline" "block" And "Test assign 1" "link" should exist in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And I should see "Assignment is due" in the "Timeline" "block" And I should see "Choice closes" in the "Timeline" "block" And I should see "Course 4" in the "Timeline" "block" And I should see "Test feedback 4" in the "Timeline" "block" And I should not see "Show more courses" in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" Scenario: Persistent sort filter Given I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by dates" "link" in the "Timeline" "block" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" When I reload the page Then I should see "Course 1" in the "Timeline" "block" And I should see "Course 2" in the "Timeline" "block" And I should see "Show more courses" in the "Timeline" "block" And I click on "Show more courses" "button" in the "Timeline" "block" And I should see "Course 4" in the ".block_timeline [data-region='view-courses']" "css_element" And "Test choice 1" "link" should exist in the "Timeline" "block" And "Test feedback 1" "link" should exist in the "Timeline" "block" And "Test feedback 4" "link" should exist in the "Timeline" "block" And "Test feedback 5" "link" should exist in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And I should not see "Course 3" in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" And "Test choice 3" "link" should not exist in the "Timeline" "block" And "Test feedback 2" "link" should not exist in the "Timeline" "block" And "Test feedback 3" "link" should not exist in the "Timeline" "block" And "Test assign 1" "link" should not exist in the "Timeline" "block" Scenario: Persistent All in course view Given I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" When I reload the page And I should not see "Course 3" in the "Timeline" "block" And I should see "Course 2" in the "Timeline" "block" And I should see "Course 1" in the "Timeline" "block" And I click on "Show more courses" "button" in the "Timeline" "block" Then I should see "Course 3" in the "Timeline" "block" And I should see "Course 2" in the "Timeline" "block" And I should see "Course 1" in the "Timeline" "block" And "Test choice 1" "link" should exist in the "Timeline" "block" And "Test choice 3" "link" should exist in the "Timeline" "block" And "Test feedback 1" "link" should exist in the "Timeline" "block" And "Test feedback 2" "link" should exist in the "Timeline" "block" And "Test feedback 3" "link" should exist in the "Timeline" "block" And "Test assign 1" "link" should exist in the "Timeline" "block" And I should not see "Show more courses" in the "Timeline" "block" And I should see "Course 4" in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" And "Test feedback 4" "link" should exist in the "Timeline" "block" Scenario: Current filtering always applies in courses view Given I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "Overdue" "link" in the "Timeline" "block" And I reload the page And "Test assign 1" "link" should exist in the "Timeline" "block" And "Test feedback 2" "link" should not exist in the "Timeline" "block" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by dates" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" # Confirm that when we switch back to courses view, the "All" filer continues to be applied (and not "overdue"). When I click on "All" "link" in the "Timeline" "block" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Show more courses" "button" in the "Timeline" "block" Then "Test assign 1" "link" should exist in the "Timeline" "block" And "Test feedback 2" "link" should exist in the "Timeline" "block" Scenario: Overdue in course view Given I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" When I click on "Overdue" "link" in the "Timeline" "block" Then "Test assign 1" "link" should exist in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And I should not see "Test feedback 1" in the "Timeline" "block" And I should not see "Test feedback 2" in the "Timeline" "block" And I should not see "Test feedback 3" in the "Timeline" "block" And I should not see "Test feedback 4" in the "Timeline" "block" And I should not see "Test feedback 5" in the "Timeline" "block" And I should not see "Test choice 1" in the "Timeline" "block" And I should not see "Test choice 2" in the "Timeline" "block" And I should not see "Test choice 3" in the "Timeline" "block" Scenario: Persistent Overdue in course view Given I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" When I click on "Overdue" "link" in the "Timeline" "block" And I reload the page Then "Test assign 1" "link" should exist in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And "Test feedback 1" "link" should not exist in the "Timeline" "block" And "Test feedback 2" "link" should not exist in the "Timeline" "block" And "Test feedback 3" "link" should not exist in the "Timeline" "block" And "Test feedback 4" "link" should not exist in the "Timeline" "block" And "Test feedback 5" "link" should not exist in the "Timeline" "block" And "Test choice 1" "link" should not exist in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" And "Test choice 3" "link" should not exist in the "Timeline" "block" Scenario: Student not enrolled in any courses sees a message Given I log in as "student2" When I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" Then I should see "No in-progress courses" in the "Timeline" "block" And I should not see "Test choice 1" Scenario: Only courses with matching events are displayed and are refreshed when filtering changes Given the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | duedate | | assign | C5 | assign3 | Test assign 3 | Test assign description | ##yesterday## | ##now +1 minute## | And the following "course enrolments" exist: | user | course | role | | student1 | C5 | student | When I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" And I click on "Show more courses" "button" in the "Timeline" "block" And I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I click on "Show more courses" "button" in the "Timeline" "block" And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test feedback 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "Overdue" "link" in the "Timeline" "block" Then I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 3" in the "Timeline" "block" And I should not see "Course 5" in the "Timeline" "block" And I should not see "Show more courses" in the "Timeline" "block" And "Test assign 1" "link" should exist in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And "Test assign 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 2" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test feedback 5" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "Next 7 days" "link" in the "Timeline" "block" And I click on "Show more courses" "button" in the "Timeline" "block" And I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 3" in the "Timeline" "block" And I should not see "Show more courses" in the "Timeline" "block" And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test feedback 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 2" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" PK'9\Ȯ,,2timeline/tests/behat/block_timeline_search.featurenu[@block @block_timeline @javascript Feature: The timeline block allows users to search for upcoming activities As a student I can search for the upcoming activities in the timeline block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | startdate | enddate | | Course 1 | C1 | 0 | ##1 month ago## | ##15 days ago## | | Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## | | Course 3 | C3 | 0 | ##first day of next month## | ##last day of next month## | | Course 4 | C4 | 0 | ##1 month ago## | ##tomorrow## | | Course 5 | C5 | 0 | ##first day of last month## | ##last day of next month## | | Course with advanced name | C6 | 0 | ##first day of last month## | ##last day of next month## | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | timeclose | duedate | | choice | C2 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## | | | choice | C1 | choice2 | Test choice 2 | Test choice description | ##first day of +5 months## | ##last day of +5 months## | | | choice | C3 | choice3 | Test choice 3 | Test choice description | ##first day of +5 months## | ##last day of +10 months## | | | choice | C2 | choice4 | Test choice 4 | Test choice description | ##first day of +5 months## | ##last day of +15 months## | | | choice | C1 | choice5 | Test choice 5 | Test choice description | ##first day of +5 months## | ##last day of +20 months## | | | choice | C3 | choice6 | Test choice 6 | Test choice description | ##first day of +5 months## | ##last day of +25 months## | | | choice | C4 | choice7 | Test choice 7 | Test choice description | ##first day of +5 months## | ##last day of +5 months## | | | choice | C5 | choice8 | Test choice 8 | Test choice description | ##first day of +5 months## | ##last day of +10 months## | | | feedback | C2 | feedback1 | Test feedback 1 | Test feedback description | ##yesterday## | ##tomorrow## | | | feedback | C1 | feedback2 | Test feedback 2 | Test feedback description | ##first day of +5 months## | ##last day of +5 months## | | | feedback | C3 | feedback3 | Test feedback 3 | Test feedback description | ##first day of +5 months## | ##last day of +10 months## | | | assign | C6 | assign1 | Assign with advanced name | Test assign description | ##yesterday## | | ##tomorrow## | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | | student1 | C6 | student | Scenario: The search should return no events if I enter the wrong value Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" When I set the field "Search" in the "Timeline" "block" to "Fake example" Then I should see "No activities require action" in the "Timeline" "block" Scenario: Search for Course name Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" When I set the field "Search" in the "Timeline" "block" to "Course 1" Then I should see "Test choice 2" in the "Timeline" "block" And I should see "Test choice 5" in the "Timeline" "block" And I should see "Test feedback 2" in the "Timeline" "block" And I should not see "Test choice 1" in the "Timeline" "block" And I should not see "Test choice 3" in the "Timeline" "block" And I should not see "Test choice 4" in the "Timeline" "block" And I should not see "Test feedback 1" in the "Timeline" "block" And I should not see "Test feedback 3" in the "Timeline" "block" Scenario: Search for Course name - Advanced Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" When I set the field "Search" in the "Timeline" "block" to "Course advanced" Then I should see "Assign with advanced name" in the "Timeline" "block" Scenario: Search for Activity name Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" When I set the field "Search" in the "Timeline" "block" to "Test choice 1" And I wait until "Test choice 2" "text" does not exist Then I should see "Test choice 1" in the "Timeline" "block" And I should not see "Test choice 2" in the "Timeline" "block" And I should not see "Test choice 3" in the "Timeline" "block" And I should not see "Test choice 4" in the "Timeline" "block" And I should not see "Test choice 5" in the "Timeline" "block" And I should not see "Test choice 6" in the "Timeline" "block" And I should not see "Test feedback 1" in the "Timeline" "block" And I should not see "Test feedback 2" in the "Timeline" "block" And I should not see "Test feedback 3" in the "Timeline" "block" Scenario: Search for Activity name - Advanced Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" When I set the field "Search" in the "Timeline" "block" to "Assign advanced" Then I should see "Assign with advanced name" in the "Timeline" "block" Scenario: Search for Activity type Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" When I set the field "Search" in the "Timeline" "block" to "feedback" Then I should see "Test feedback 1" in the "Timeline" "block" And I should see "Test feedback 2" in the "Timeline" "block" And I should see "Test feedback 3" in the "Timeline" "block" And I should not see "Test choice 1" in the "Timeline" "block" And I should not see "Test choice 2" in the "Timeline" "block" And I should not see "Test choice 3" in the "Timeline" "block" And I should not see "Test choice 4" in the "Timeline" "block" And I should not see "Test choice 5" in the "Timeline" "block" Scenario: Timeline paginated search Given I log in as "student1" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" When I set the field "Search" in the "Timeline" "block" to "choice" Then I should see "Test choice 1" in the "Timeline" "block" And I should see "Test choice 2" in the "Timeline" "block" And I should see "Test choice 3" in the "Timeline" "block" And I should see "Test choice 7" in the "Timeline" "block" And I should see "Test choice 8" in the "Timeline" "block" And I should not see "Test choice 4" in the "Timeline" "block" And I should not see "Test choice 5" in the "Timeline" "block" And I should not see "Test choice 6" in the "Timeline" "block" And I click on "Show more activities" "button" And I should see "Test choice 4" in the "Timeline" "block" And I should see "Test choice 5" in the "Timeline" "block" And I should see "Test choice 6" in the "Timeline" "block" Scenario: Courses view is refreshed when search changes Given I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" And I click on "Show more courses" "button" in the "Timeline" "block" And I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I click on "Show more courses" "button" in the "Timeline" "block" And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Test choice 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Test choice 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Test choice 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Test choice 7" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Test choice 8" in the ".block-timeline [data-region='view-courses']" "css_element" When I set the field "Search by activity type or name" to "choice 1" And I wait until "//h4[contains(@class, 'h5') and contains(.,'Course 4')]" "xpath_element" does not exist Then I should see "Test choice 1" in the "Timeline" "block" And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Test choice 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Test choice 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Test choice 7" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Test choice 8" in the ".block-timeline [data-region='view-courses']" "css_element" PK'9\~J activity_results/styles.cssnu[.block_activity_results h1 { margin: 4px; font-size: 1.1em; } .block_activity_results table.grades { text-align: left; width: 100%; } .block_activity_results table.grades .number { text-align: left; width: 10%; } .block_activity_results table.grades .name { text-align: left; width: 77%; } .block_activity_results table.grades .grade { text-align: right; } PK'9\activity_results/db/access.phpnu[. /** * Activity results block caps. * * @package block_activity_results * @copyright 2015 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/activity_results:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\/_[3activity_results/lang/en/block_activity_results.phpnu[. /** * Strings for component 'block_activity_results', language 'en', branch 'MOODLE_20_STABLE' * * @package block_activity_results * @copyright 2015 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['activity_results:addinstance'] = 'Add a new activity results block'; $string['bestgrade'] = 'The highest grade:'; $string['bestgrades'] = 'The {$a} highest grades:'; $string['bestgroupgrade'] = 'The group with the highest average:'; $string['bestgroupgrades'] = 'The {$a} groups with the highest average:'; $string['config_format_absolute'] = 'Absolute numbers'; $string['config_format_fraction'] = 'Fractions'; $string['config_format_percentage'] = 'Percentages'; $string['config_decimalplaces'] = 'Decimal places to display'; $string['config_grade_format'] = 'Display grades as'; $string['config_name_format'] = 'Privacy of results'; $string['config_names_anon'] = 'Anonymous results'; $string['config_names_full'] = 'Display full names'; $string['config_names_id'] = 'Display only ID numbers'; $string['config_no_activities_in_course'] = 'There are not yet any activities in this course.'; $string['config_select_activity'] = 'Which activity should this block display results from?'; $string['config_show_best'] = 'How many of the highest grades should be shown (0 to disable)?'; $string['config_show_worst'] = 'How many of the lowest grades should be shown (0 to disable)?'; $string['configuredtoshownothing'] = 'This block\'s configuration currently does not allow it to show any results.'; $string['config_use_groups'] = 'Show groups instead of students (only if the activity supports groups)?'; $string['defaulthighestgrades'] = 'Default highest grades shown'; $string['defaulthighestgrades_desc'] = 'How many of the highest grades should be shown by default?'; $string['defaultlowestgrades'] = 'Default lowest grades shown'; $string['defaultlowestgrades_desc'] = 'How many of the lowest grades should be shown by default?'; $string['defaultshowgroups'] = 'Default show groups'; $string['defaultnameoptions'] = 'Privacy of results'; $string['defaultnameoptions_desc'] = 'How should the students be identified by default?'; $string['defaultshowgroups_desc'] = 'Show groups instead of students by default (only if the activity supports groups)'; $string['defaultgradedisplay'] = 'Display grades as'; $string['defaultgradedisplay_desc'] = 'How should the grades be displayed by default?'; $string['defaultdecimalplaces'] = 'Decimal places'; $string['defaultdecimalplaces_desc'] = 'Number of decimal places to display by default'; $string['error_emptyactivityid'] = 'Please configure this block and select which activity it should display results from.'; $string['error_emptyactivityrecord'] = 'Error: the selected activity does not exist in the database.'; $string['error_nogroupsexist'] = 'Error: the block is set to display grades in group mode, but there are no groups defined.'; $string['error_unsupportedgradetype'] = 'Error: the activity selected uses a grading method that is not supported by this block.'; $string['notyetgraded'] = 'Not yet graded'; $string['pluginname'] = 'Activity results'; $string['unknown'] = 'Unknown scale'; $string['worstgrade'] = 'The lowest grade:'; $string['worstgrades'] = 'The {$a} lowest grades:'; $string['worstgroupgrade'] = 'The group with the lowest average:'; $string['worstgroupgrades'] = 'The {$a} groups with the lowest average:'; $string['privacy:metadata'] = 'The Activity results block only shows data stored in other locations.'; PK'9\dPMactivity_results/backup/moodle2/restore_activity_results_block_task.class.phpnu[. /** * Define all the backup steps that will be used by the backup_block_task * @package block_activity_results * @copyright 2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @copyright 2015 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Specialised restore task for the activity_results block * (using execute_after_tasks for recoding of target activity) * * @copyright 2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class restore_activity_results_block_task extends restore_block_task { /** * Define (add) particular settings this activity can have */ protected function define_my_settings() { } /** * Define (add) particular steps this activity can have */ protected function define_my_steps() { } /** * Define the associated file areas */ public function get_fileareas() { return array(); // No associated fileareas. } /** * Define special handling of configdata. */ public function get_configdata_encoded_attributes() { return array(); // No special handling of configdata. } /** * This function, executed after all the tasks in the plan * have been executed, will perform the recode of the * target activity for the block. This must be done here * and not in normal execution steps because the activity * can be restored after the block. */ public function after_restore() { global $DB; // Get the blockid. $blockid = $this->get_blockid(); if ($configdata = $DB->get_field('block_instances', 'configdata', array('id' => $blockid))) { $config = $this->decode_configdata($configdata); if (!empty($config->activityparentid)) { // Get the mapping and replace it in config. if ($mapping = restore_dbops::get_backup_ids_record($this->get_restoreid(), $config->activityparent, $config->activityparentid)) { // Update the parent module id (the id from mdl_quiz etc...) $config->activityparentid = $mapping->newitemid; // Get the grade_items record to update the activitygradeitemid. $info = $DB->get_record('grade_items', array('iteminstance' => $config->activityparentid, 'itemmodule' => $config->activityparent)); // Update the activitygradeitemid the id from the grade_items table. $config->activitygradeitemid = $info->id; // Encode and save the config. $configdata = base64_encode(serialize($config)); $DB->set_field('block_instances', 'configdata', $configdata, array('id' => $blockid)); } } } } /** * Define the contents in the activity that must be * processed by the link decoder */ public static function define_decode_contents() { return array(); } /** * Define the decoding rules for links belonging * to the activity to be executed by the link decoder */ public static function define_decode_rules() { return array(); } } PK'9\<activity_results/edit_form.phpnu[. /** * Defines the form for editing Quiz results block instances. * * @package block_activity_results * @copyright 2009 Tim Hunt * @copyright 2015 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/lib/grade/constants.php'); /** * Form for editing activity results block instances. * * @copyright 2009 Tim Hunt * @copyright 2015 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_activity_results_edit_form extends block_edit_form { /** * The definition of the fields to use. * * @param MoodleQuickForm $mform */ protected function specific_definition($mform) { global $DB; // Load defaults. $blockconfig = get_config('block_activity_results'); // Fields for editing activity_results block title and contents. $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); // Get supported modules (Only modules using grades or scales will be listed). $sql = 'SELECT id, itemname FROM {grade_items} WHERE courseid = ? and itemtype = ? and (gradetype = ? or gradetype = ?)'; $params = array($this->page->course->id, 'mod', GRADE_TYPE_VALUE, GRADE_TYPE_SCALE); $activities = $DB->get_records_sql_menu($sql, $params); core_collator::asort($activities); if (empty($activities)) { $mform->addElement('static', 'noactivitieswarning', get_string('config_select_activity', 'block_activity_results'), get_string('config_no_activities_in_course', 'block_activity_results')); } else { foreach ($activities as $id => $name) { $activities[$id] = strip_tags(format_string($name)); } $mform->addElement('select', 'config_activitygradeitemid', get_string('config_select_activity', 'block_activity_results'), $activities); $mform->setDefault('config_activitygradeitemid', $this->block->get_owning_activity()->id); } $mform->addElement('text', 'config_showbest', get_string('config_show_best', 'block_activity_results'), array('size' => 3)); $mform->setDefault('config_showbest', $blockconfig->config_showbest); $mform->setType('config_showbest', PARAM_INT); if ($blockconfig->config_showbest_locked) { $mform->freeze('config_showbest'); } $mform->addElement('text', 'config_showworst', get_string('config_show_worst', 'block_activity_results'), array('size' => 3)); $mform->setDefault('config_showworst', $blockconfig->config_showworst); $mform->setType('config_showworst', PARAM_INT); if ($blockconfig->config_showworst_locked) { $mform->freeze('config_showworst'); } $mform->addElement('selectyesno', 'config_usegroups', get_string('config_use_groups', 'block_activity_results')); $mform->setDefault('config_usegroups', $blockconfig->config_usegroups); if ($blockconfig->config_usegroups_locked) { $mform->freeze('config_usegroups'); } $nameoptions = array( B_ACTIVITYRESULTS_NAME_FORMAT_FULL => get_string('config_names_full', 'block_activity_results'), B_ACTIVITYRESULTS_NAME_FORMAT_ID => get_string('config_names_id', 'block_activity_results'), B_ACTIVITYRESULTS_NAME_FORMAT_ANON => get_string('config_names_anon', 'block_activity_results') ); $mform->addElement('select', 'config_nameformat', get_string('config_name_format', 'block_activity_results'), $nameoptions); $mform->setDefault('config_nameformat', $blockconfig->config_nameformat); if ($blockconfig->config_nameformat_locked) { $mform->freeze('config_nameformat'); } $gradeeoptions = array( B_ACTIVITYRESULTS_GRADE_FORMAT_PCT => get_string('config_format_percentage', 'block_activity_results'), B_ACTIVITYRESULTS_GRADE_FORMAT_FRA => get_string('config_format_fraction', 'block_activity_results'), B_ACTIVITYRESULTS_GRADE_FORMAT_ABS => get_string('config_format_absolute', 'block_activity_results') ); $mform->addElement('select', 'config_gradeformat', get_string('config_grade_format', 'block_activity_results'), $gradeeoptions); $mform->setDefault('config_gradeformat', $blockconfig->config_gradeformat); if ($blockconfig->config_gradeformat_locked) { $mform->freeze('config_gradeformat'); } $options = array(); for ($i = 0; $i <= 5; $i++) { $options[$i] = $i; } $mform->addElement('select', 'config_decimalpoints', get_string('config_decimalplaces', 'block_activity_results'), $options); $mform->setDefault('config_decimalpoints', $blockconfig->config_decimalpoints); $mform->setType('config_decimalpoints', PARAM_INT); if ($blockconfig->config_decimalpoints_locked) { $mform->freeze('config_decimalpoints'); } } /** * Display the configuration form when block is being added to the page * * @return bool */ public static function display_form_when_adding(): bool { return true; } }PK'9\s܄activity_results/version.phpnu[. /** * Version information for the block_activity_results plugin. * * @package block_activity_results * @copyright 2015 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_activity_results'; // Full name of the plugin (used for diagnostics).PK'9\uԔ-activity_results/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_activity_results. * * @package block_activity_results * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_activity_results\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_activity_results implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\t;activity_results/tests/behat/addunsupportedactivity.featurenu[@block @block_activity_results Feature: The activity results block doesn't display student scores for unsupported activity In order to be display student scores As a user I need to properly configure the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Try to configure the block to use an activity without grades Given the following "activities" exist: | activity | name | intro | course | section | idnumber | assignsubmission_file_enabled | | assign | Test assignment | Offline text | C1 | 1 | assign1 | 0 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | activity_results | Course | C1 | course-view-* | side-pre | And I am on "Course 1" course homepage And I configure the "Activity results" block And I set the following fields to these values: | config_showbest | 1 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display full names | And I press "Save changes" When I am on the "Test assignment" "assign activity editing" page And I set the following fields to these values: | id_grade_modgrade_type | None | And I press "Save and return to course" Then I should see "Error: the activity selected uses a grading method that is not supported by this block." in the "Activity results" "block" PK'9\64K8activity_results/tests/behat/lowscoreswithscales.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays student low scores as scales In order to be display student scores as scales As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | Given the following "activity" exists: | activity | assign | | name | Test assignment | | intro | Offline text | | course | C1 | | idnumber | 0001 | | section | 1 | | assignsubmission_file_enabled | 0 | And the following "scales" exist: | name | scale | | My Scale | Disappointing, Not good enough, Average, Good, Very good, Excellent! | And I am on the "Test assignment" "assign activity editing" page logged in as teacher1 And I set the following fields to these values: | id_grade_modgrade_type | Scale | | id_grade_modgrade_scale | My Scale | And I press "Save and return to course" And I turn editing mode on And I am on the "Course 1" "grades > Grader report > View" page And I give the grade "Excellent!" to the user "Student 1" for the grade item "Test assignment" And I give the grade "Very good" to the user "Student 2" for the grade item "Test assignment" And I give the grade "Good" to the user "Student 3" for the grade item "Test assignment" And I give the grade "Average" to the user "Student 4" for the grade item "Test assignment" And I give the grade "Not good enough" to the user "Student 5" for the grade item "Test assignment" And I press "Save changes" And I am on "Course 1" course homepage Scenario: Configure the block on the course page to show 1 low score Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | Then I should see "Student 5" in the "Activity results" "block" And I should see "Not good enough" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores using full names Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 3 | | config_nameformat | Display full names | Then I should see "Student 5" in the "Activity results" "block" And I should see "Not good enough" in the "Activity results" "block" And I should see "Student 4" in the "Activity results" "block" And I should see "Average" in the "Activity results" "block" And I should see "Student 3" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores using ID numbers Given the following config values are set as admin: | showuseridentity | idnumber,email | And I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 3 | | config_nameformat | Display only ID numbers | Then I should see "User S5" in the "Activity results" "block" And I should see "Not good enough" in the "Activity results" "block" And I should see "User S4" in the "Activity results" "block" And I should see "Average" in the "Activity results" "block" And I should see "User S3" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores using anonymous names Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 3 | | config_nameformat | Anonymous results | Then I should see "User" in the "Activity results" "block" And I should not see "Student 5" in the "Activity results" "block" And I should see "Not good enough" in the "Activity results" "block" And I should not see "Student 4" in the "Activity results" "block" And I should see "Average" in the "Activity results" "block" And I should not see "Student 3" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" PK'9\'e.&&Aactivity_results/tests/behat/highscoreswithseperategroups.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays student in separate groups scores In order to be display student scores As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | | student6 | Student | 6 | student6@example.com | S6 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | | Group 2 | C1 | G2 | | Group 3 | C1 | G3 | | Group 4 | C1 | G4 | | Group 5 | C1 | G5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | | student6 | C1 | student | And the following "group members" exist: | user | group | | student1 | G1 | | student2 | G1 | | student3 | G2 | | student4 | G2 | | student5 | G3 | | student6 | G3 | And the following "activity" exists: | activity | assign | | course | C1 | | idnumber | 0001 | | name | Test assignment | | section | 1 | | assignsubmission_file_enabled | 0 | | groupmode | 1 | And the following "grade grades" exist: | gradeitem | user | grade | | Test assignment | student1 | 100.00 | | Test assignment | student2 | 90.00 | | Test assignment | student3 | 90.00 | | Test assignment | student4 | 80.00 | | Test assignment | student5 | 80.00 | | Test assignment | student6 | 70.00 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Configure the block on the course page to show 1 high score Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | | config_usegroups | Yes | Then I should see "Group 1" in the "Activity results" "block" And I should see "95%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 high score as a fraction Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_gradeformat | Fractions | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 1" in the "Activity results" "block" And I should see "95.00/100.00" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student1 And I should see "Student 1" in the "Activity results" "block" And I should see "100.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 high score as a absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 1" in the "Activity results" "block" And I should see "95.00" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student1 And I should see "Student 1" in the "Activity results" "block" And I should see "100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores as percentages Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | | config_usegroups | Yes | Then I should see "Group 1" in the "Activity results" "block" And I should see "95%" in the "Activity results" "block" And I should see "Group 2" in the "Activity results" "block" And I should see "85%" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75%" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student1 And I should see "Student 1" in the "Activity results" "block" And I should see "100%" in the "Activity results" "block" And I should see "Student 2" in the "Activity results" "block" And I should see "90%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores as fractions Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Fractions | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 1" in the "Activity results" "block" And I should see "95.00/100.00" in the "Activity results" "block" And I should see "Group 2" in the "Activity results" "block" And I should see "85.00/100.00" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75.00/100.00" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student3 And I should see "Student 3" in the "Activity results" "block" And I should see "90.00/100.00" in the "Activity results" "block" And I should see "Student 4" in the "Activity results" "block" And I should see "80.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores as absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 1" in the "Activity results" "block" And I should see "95.00" in the "Activity results" "block" And I should see "Group 2" in the "Activity results" "block" And I should see "85.00" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75.00" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student1 And I should see "Student 1" in the "Activity results" "block" And I should see "100.00" in the "Activity results" "block" And I should see "Student 2" in the "Activity results" "block" And I should see "90.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using ID numbers Given the following config values are set as admin: | showuseridentity | idnumber,email | And I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display only ID numbers | | config_usegroups | Yes | Then I should see "Group" in the "Activity results" "block" And I should see "95.00%" in the "Activity results" "block" And I should see "85.00%" in the "Activity results" "block" And I should see "75.00%" in the "Activity results" "block" # Students cannot see user identity fields. And I am on the "Course 1" course page logged in as student1 And I should see "User" in the "Activity results" "block" And I should not see "User S1" in the "Activity results" "block" And I should see "100.00%" in the "Activity results" "block" And I should not see "User S2" in the "Activity results" "block" And I should see "90.00%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using anonymous names Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Anonymous results | | config_usegroups | Yes | Then I should see "Group" in the "Activity results" "block" And I should see "95.00%" in the "Activity results" "block" And I should see "85.00%" in the "Activity results" "block" And I should see "75.00%" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student1 And I should see "User" in the "Activity results" "block" And I should see "100.00%" in the "Activity results" "block" And I should see "90.00%" in the "Activity results" "block" PK'9\/ʥ[[Aactivity_results/tests/behat/lowscoreswithscalesandgroups.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays students in groups low scores as scales In order to be display student scores as scales As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | | student6 | Student | 6 | student6@example.com | S6 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | | Group 2 | C1 | G2 | | Group 3 | C1 | G3 | | Group 4 | C1 | G4 | | Group 5 | C1 | G5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | | student6 | C1 | student | And the following "group members" exist: | user | group | | student1 | G1 | | student2 | G1 | | student3 | G2 | | student4 | G2 | | student5 | G3 | | student6 | G3 | And the following "activity" exists: | activity | assign | | course | C1 | | idnumber | 0001 | | name | Test assignment | | description | Offline text | | assignsubmission_file_enabled | 0 | | groupmode | 1 | And the following "scales" exist: | name | scale | | My Scale | Disappointing, Not good enough, Average, Good, Very good, Excellent! | And I change window size to "large" And I am on the "Test assignment" "assign activity editing" page logged in as teacher1 And I set the following fields to these values: | id_grade_modgrade_type | Scale | | id_grade_modgrade_scale | My Scale | And I press "Save and return to course" And I am on the "Course 1" "grades > Grader report > View" page And I turn editing mode on And I give the grade "Excellent!" to the user "Student 1" for the grade item "Test assignment" And I give the grade "Very good" to the user "Student 2" for the grade item "Test assignment" And I give the grade "Very good" to the user "Student 3" for the grade item "Test assignment" And I give the grade "Good" to the user "Student 4" for the grade item "Test assignment" And I give the grade "Good" to the user "Student 5" for the grade item "Test assignment" And I give the grade "Average" to the user "Student 6" for the grade item "Test assignment" And I press "Save changes" And I am on "Course 1" course homepage Scenario: Try to configure the block on the course page to show 1 low score Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 3" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student5 And I should see "Student 6" in the "Activity results" "block" And I should see "Average" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using full names Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 2" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student3 And I should see "Student 3" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should see "Student 4" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using ID numbers Given the following config values are set as admin: | showuseridentity | idnumber,email | And I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_nameformat | Display only ID numbers | | config_usegroups | Yes | Then I should see "Group" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" # Students cannot see user identity fields. And I am on the "Course 1" course page logged in as student5 And I should see "User" in the "Activity results" "block" And I should not see "User S5" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" And I should not see "User S6" in the "Activity results" "block" And I should see "Average" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using anonymous names Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_nameformat | Anonymous results | | config_usegroups | Yes | Then I should see "Group" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student5 And I should see "User" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" And I should see "Average" in the "Activity results" "block" PK'9\117activity_results/tests/behat/addblockinactivity.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays student scores In order to be display student scores As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | And the following "activities" exist: | activity | name | intro | course | section | idnumber | assignsubmission_file_enabled | | assign | Test assignment 1 | Offline text | C1 | 1 | assign1 | 0 | | assign | Test assignment 2 | Offline text | C1 | 1 | assign2 | 0 | | assign | Test assignment 3 | Offline text | C1 | 1 | assign3 | 0 | And the following "activities" exist: | activity | name | content | course | section | idnumber | | page | Test page name | This is a page | C1 | 1 | page1 | And the following "grade grades" exist: | gradeitem | user | grade | | Test assignment 1 | student1 | 90.00 | | Test assignment 1 | student2 | 80.00 | | Test assignment 1 | student3 | 70.00 | | Test assignment 1 | student4 | 60.00 | | Test assignment 1 | student5 | 50.00 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Configure the block on a non-graded activity to show 3 high scores Given I am on the "Test page name" "page activity" page And I add the "Activity results" block to the default region with: | config_activitygradeitemid | Test assignment 1 | | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | Then I should see "Student 1" in the "Activity results" "block" And I should see "90.00" in the "Activity results" "block" And I should see "Student 2" in the "Activity results" "block" And I should see "80.00" in the "Activity results" "block" And I should see "Student 3" in the "Activity results" "block" And I should see "70.00" in the "Activity results" "block" @javascript @addablocklink Scenario: Block should select current activity by default Given I click on "Test assignment 1" "link" in the "region-main" "region" When I add the "Activity results..." block Then the field "config_activitygradeitemid" in the "Add Activity results block" "dialogue" matches value "Test assignment 1" And I click on "Save changes" "button" in the "Add Activity results block" "dialogue" And I am on "Course 1" course homepage And I click on "Test assignment 2" "link" in the "region-main" "region" And I add the "Activity results..." block And the field "config_activitygradeitemid" in the "Add Activity results block" "dialogue" matches value "Test assignment 2" And I click on "Save changes" "button" in the "Add Activity results block" "dialogue" And I am on "Course 1" course homepage And I click on "Test assignment 3" "link" in the "region-main" "region" And I add the "Activity results..." block And the field "config_activitygradeitemid" in the "Add Activity results block" "dialogue" matches value "Test assignment 3" And I click on "Save changes" "button" in the "Add Activity results block" "dialogue" And I am on "Course 1" course homepage And I click on "Test page name" "link" in the "region-main" "region" And I add the "Activity results..." block And the field "config_activitygradeitemid" in the "Add Activity results block" "dialogue" does not match value "Test page name" And I click on "Save changes" "button" in the "Add Activity results block" "dialogue" PK'9\1!00<activity_results/tests/behat/highscoreswithoutgroups.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays student high scores In order to be display student scores As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | And the following "activities" exist: | activity | name | intro | course | section | idnumber | assignsubmission_file_enabled | | assign | Test assignment | Offline text | C1 | 1 | assign1 | 0 | And the following "grade grades" exist: | gradeitem | user | grade | | Test assignment | student1 | 90.00 | | Test assignment | student2 | 80.00 | | Test assignment | student3 | 70.00 | | Test assignment | student4 | 60.00 | | Test assignment | student5 | 50.00 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Configure the block on the course page to show 0 high scores Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display full names | Then I should see "This block's configuration currently does not allow it to show any results." in the "Activity results" "block" Scenario: Configure the block on the course page to show 1 high score Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | Then I should see "Student 1" in the "Activity results" "block" And I should see "90%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 high score as a fraction Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_gradeformat | Fractions | | config_nameformat | Display full names | Then I should see "Student 1" in the "Activity results" "block" And I should see "90.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 high score as a absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | Then I should see "Student 1" in the "Activity results" "block" And I should see "90.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores as percentages Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | Then I should see "Student 1" in the "Activity results" "block" And I should see "90%" in the "Activity results" "block" And I should see "Student 2" in the "Activity results" "block" And I should see "80%" in the "Activity results" "block" And I should see "Student 3" in the "Activity results" "block" And I should see "70%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores as fractions Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Fractions | | config_nameformat | Display full names | Then I should see "Student 1" in the "Activity results" "block" And I should see "90.00/100.00" in the "Activity results" "block" And I should see "Student 2" in the "Activity results" "block" And I should see "80.00/100.00" in the "Activity results" "block" And I should see "Student 3" in the "Activity results" "block" And I should see "70.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores as absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | Then I should see "Student 1" in the "Activity results" "block" And I should see "90.00" in the "Activity results" "block" And I should see "Student 2" in the "Activity results" "block" And I should see "80.00" in the "Activity results" "block" And I should see "Student 3" in the "Activity results" "block" And I should see "70.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using ID numbers Given the following config values are set as admin: | showuseridentity | idnumber,email | And I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display only ID numbers | Then I should see "User S1" in the "Activity results" "block" And I should see "90.00%" in the "Activity results" "block" And I should see "User S2" in the "Activity results" "block" And I should see "80.00%" in the "Activity results" "block" And I should see "User S3" in the "Activity results" "block" And I should see "70.00%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using anonymous names Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Anonymous results | Then I should see "User" in the "Activity results" "block" And I should see "90.00%" in the "Activity results" "block" And I should see "80.00%" in the "Activity results" "block" And I should see "70.00%" in the "Activity results" "block" PK'9\)Bactivity_results/tests/behat/highscoreswithscalesandgroups.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays student in group high scores as scales In order to be display student scores as scales As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | | student6 | Student | 6 | student6@example.com | S6 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | | Group 2 | C1 | G2 | | Group 3 | C1 | G3 | | Group 4 | C1 | G4 | | Group 5 | C1 | G5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | | student6 | C1 | student | And the following "group members" exist: | user | group | | student1 | G1 | | student2 | G1 | | student3 | G2 | | student4 | G2 | | student5 | G3 | | student6 | G3 | And the following "activities" exist: | activity | name | intro | course | section | idnumber | | assign | Test assignment | Offline text | C1 | 1 | assign1 | And the following "scales" exist: | name | scale | | My Scale | Disappointing, Not good enough, Average, Good, Very good, Excellent! | And I change window size to "large" And I am on the "Test assignment" "assign activity editing" page logged in as teacher1 And I set the following fields to these values: | assignsubmission_file_enabled | 0 | | id_grade_modgrade_type | Scale | | id_grade_modgrade_scale | My Scale | | Group mode | Separate groups | And I press "Save and return to course" And I turn editing mode on And I am on the "Course 1" "grades > Grader report > View" page And I give the grade "Excellent!" to the user "Student 1" for the grade item "Test assignment" And I give the grade "Very good" to the user "Student 2" for the grade item "Test assignment" And I give the grade "Very good" to the user "Student 3" for the grade item "Test assignment" And I give the grade "Good" to the user "Student 4" for the grade item "Test assignment" And I give the grade "Good" to the user "Student 5" for the grade item "Test assignment" And I give the grade "Average" to the user "Student 6" for the grade item "Test assignment" And I press "Save changes" And I am on "Course 1" course homepage Scenario: Try to configure the block on the course page to show 1 high score Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 1" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student1 And I should see "Student 1" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using full names Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 1" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" And I should see "Group 2" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student3 And I should see "Student 3" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should see "Student 4" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using ID numbers Given the following config values are set as admin: | showuseridentity | idnumber,email | And I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_nameformat | Display only ID numbers | | config_usegroups | Yes | Then I should see "Group" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" # Students cannot see user identity fields. And I am on the "Course 1" course page logged in as student1 And I should see "User" in the "Activity results" "block" And I should not see "User S1" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" And I should not see "User S2" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using anonymous names Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_nameformat | Anonymous results | | config_usegroups | Yes | Then I should see "Group" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student1 And I should see "User" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" PK'9\n;activity_results/tests/behat/lowscoreswithoutgroups.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays student low scores In order to be display student scores As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | And the following "activity" exists: | activity | assign | | course | C1 | | idnumber | 0001 | | name | Test assignment | | assignsubmission_file_enabled | 0 | And the following "grade grades" exist: | gradeitem | user | grade | | Test assignment | student1 | 90.00 | | Test assignment | student2 | 80.00 | | Test assignment | student3 | 70.00 | | Test assignment | student4 | 60.00 | | Test assignment | student5 | 50.00 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Configure the block on the course page to show 1 low score Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | Then I should see "Student 5" in the "Activity results" "block" And I should see "50%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 low score as a fraction Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_gradeformat | Fractions | | config_nameformat | Display full names | Then I should see "Student 5" in the "Activity results" "block" And I should see "50.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 low score as a absolute number Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | Then I should see "Student 5" in the "Activity results" "block" And I should see "50.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores as percentages Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 3 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | Then I should see "Student 5" in the "Activity results" "block" And I should see "50%" in the "Activity results" "block" And I should see "Student 4" in the "Activity results" "block" And I should see "60%" in the "Activity results" "block" And I should see "Student 3" in the "Activity results" "block" And I should see "70%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores as fractions Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 3 | | config_gradeformat | Fractions | | config_nameformat | Display full names | Then I should see "Student 5" in the "Activity results" "block" And I should see "50.00/100.00" in the "Activity results" "block" And I should see "Student 4" in the "Activity results" "block" And I should see "60.00/100.00" in the "Activity results" "block" And I should see "Student 3" in the "Activity results" "block" And I should see "70.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores as absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 3 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | Then I should see "Student 5" in the "Activity results" "block" And I should see "50.00" in the "Activity results" "block" And I should see "Student 4" in the "Activity results" "block" And I should see "60.00" in the "Activity results" "block" And I should see "Student 3" in the "Activity results" "block" And I should see "70.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores using ID numbers Given the following config values are set as admin: | showuseridentity | idnumber,email | And I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 3 | | config_gradeformat | Percentages | | config_nameformat | Display only ID numbers | Then I should see "User S5" in the "Activity results" "block" And I should see "50.00%" in the "Activity results" "block" And I should see "User S4" in the "Activity results" "block" And I should see "60.00%" in the "Activity results" "block" And I should see "User S3" in the "Activity results" "block" And I should see "70.00%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores using anonymous names Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 3 | | config_gradeformat | Percentages | | config_nameformat | Anonymous results | Then I should see "User" in the "Activity results" "block" And I should see "50.00%" in the "Activity results" "block" And I should see "60.00%" in the "Activity results" "block" And I should see "70.00%" in the "Activity results" "block" PK'9\ɂqq?activity_results/tests/behat/lowscoreswithvisiblegroups.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays student in visible groups low scores In order to be display student scores As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | | student6 | Student | 6 | student6@example.com | S6 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | | Group 2 | C1 | G2 | | Group 3 | C1 | G3 | | Group 4 | C1 | G4 | | Group 5 | C1 | G5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | | student6 | C1 | student | And the following "group members" exist: | user | group | | student1 | G1 | | student2 | G1 | | student3 | G2 | | student4 | G2 | | student5 | G3 | | student6 | G3 | And the following "activity" exists: | activity | assign | | course | C1 | | idnumber | 0001 | | name | Test assignment | | assignsubmission_file_enabled | 0 | | groupmode | 2 | And the following "grade grades" exist: | gradeitem | user | grade | | Test assignment | student1 | 100.00 | | Test assignment | student2 | 90.00 | | Test assignment | student3 | 90.00 | | Test assignment | student4 | 80.00 | | Test assignment | student5 | 80.00 | | Test assignment | student6 | 70.00 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Configure the block on the course page to show 1 low score Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | | config_usegroups | Yes | Then I should see "Group 3" in the "Activity results" "block" And I should see "75%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 low score as a fraction Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_gradeformat | Fractions | | config_nameformat | Display full names | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group 3" in the "Activity results" "block" And I should see "75.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 low score as a absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | | config_usegroups | Yes | When I am on the "Course 1" course page logged in as student1 Then I should see "Group 3" in the "Activity results" "block" And I should see "75.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores as percentages Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | | config_usegroups | Yes | Then I should see "Group 2" in the "Activity results" "block" And I should see "85%" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75%" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student5 Then I should see "Group 2" in the "Activity results" "block" And I should see "85%" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores as fractions Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_gradeformat | Fractions | | config_nameformat | Display full names | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group 2" in the "Activity results" "block" And I should see "85.00/100.00" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores as absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group 2" in the "Activity results" "block" And I should see "85.00" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores using ID numbers Given the following config values are set as admin: | showuseridentity | idnumber,email | And I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_gradeformat | Percentages | | config_nameformat | Display only ID numbers | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group" in the "Activity results" "block" And I should see "85.00%" in the "Activity results" "block" And I should see "75.00%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores using anonymous names Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_gradeformat | Percentages | | config_nameformat | Anonymous results | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group" in the "Activity results" "block" And I should see "85.00%" in the "Activity results" "block" And I should see "75.00%" in the "Activity results" "block" PK'9\H-#-#@activity_results/tests/behat/lowscoreswithseperategroups.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays students in separate groups scores In order to be display student scores As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | | student6 | Student | 6 | student6@example.com | S6 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | | Group 2 | C1 | G2 | | Group 3 | C1 | G3 | | Group 4 | C1 | G4 | | Group 5 | C1 | G5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | | student6 | C1 | student | And the following "group members" exist: | user | group | | student1 | G1 | | student2 | G1 | | student3 | G2 | | student4 | G2 | | student5 | G3 | | student6 | G3 | And the following "activities" exist: | activity | course | idnumber | name | assignsubmission_file_enabled | groupmode | | assign | C1 | a1 | Test assignment | 0 | 1 | And the following "grade grades" exist: | gradeitem | user | grade | | Test assignment | student1 | 100.00 | | Test assignment | student2 | 90.00 | | Test assignment | student3 | 90.00 | | Test assignment | student4 | 80.00 | | Test assignment | student5 | 80.00 | | Test assignment | student6 | 70.00 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Configure the block on the course page to show 1 low score Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | | config_usegroups | Yes | Then I should see "Group 3" in the "Activity results" "block" And I should see "75%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 low score as a fraction Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_gradeformat | Fractions | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 3" in the "Activity results" "block" And I should see "75.00/100.00" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student5 And I should see "Student 6" in the "Activity results" "block" And I should see "70.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 low score as a absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 1 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 3" in the "Activity results" "block" And I should see "75.00" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student5 And I should see "Student 6" in the "Activity results" "block" And I should see "70.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores as percentages Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | | config_usegroups | Yes | Then I should see "Group 2" in the "Activity results" "block" And I should see "85%" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75%" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student5 And I should see "Student 6" in the "Activity results" "block" And I should see "70%" in the "Activity results" "block" And I should see "Student 5" in the "Activity results" "block" And I should see "80%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores as fractions Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_gradeformat | Fractions | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 2" in the "Activity results" "block" And I should see "85.00/100.00" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75.00/100.00" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student3 And I should see "Student 3" in the "Activity results" "block" And I should see "90.00/100.00" in the "Activity results" "block" And I should see "Student 4" in the "Activity results" "block" And I should see "80.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores as absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | | config_usegroups | Yes | Then I should see "Group 2" in the "Activity results" "block" And I should see "85.00" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75.00" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student5 And I should see "Student 5" in the "Activity results" "block" And I should see "80.00" in the "Activity results" "block" And I should see "Student 6" in the "Activity results" "block" And I should see "70.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores using ID numbers Given the following config values are set as admin: | showuseridentity | idnumber,email | And I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_gradeformat | Percentages | | config_nameformat | Display only ID numbers | | config_usegroups | Yes | Then I should see "Group" in the "Activity results" "block" And I should see "85.00%" in the "Activity results" "block" And I should see "75.00%" in the "Activity results" "block" # Students cannot see user identity fields. And I am on the "Course 1" course page logged in as student1 And I should see "User" in the "Activity results" "block" And I should not see "User S1" in the "Activity results" "block" And I should see "100.00%" in the "Activity results" "block" And I should not see "User S2" in the "Activity results" "block" And I should see "90.00%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple low scores using anonymous names Given I add the "Activity results" block to the default region with: | config_showbest | 0 | | config_showworst | 2 | | config_gradeformat | Percentages | | config_nameformat | Anonymous results | | config_usegroups | Yes | Then I should see "Group" in the "Activity results" "block" And I should see "85.00%" in the "Activity results" "block" And I should see "75.00%" in the "Activity results" "block" And I am on the "Course 1" course page logged in as student1 And I should see "User" in the "Activity results" "block" And I should see "100.00%" in the "Activity results" "block" And I should see "90.00%" in the "Activity results" "block" PK'9\- 9activity_results/tests/behat/addunconfiguredblock.featurenu[@block @block_activity_results Feature: The activity results block doesn't displays student scores for unconfigured block In order to be display student scores As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Add the block to a the course with Javascript disabled Given I add the "Activity results" block Then I should see "Please configure this block and select which activity it should display results from." in the "Activity results" "block" @javascript Scenario: Add the block to a the course with Javascript enabled Given I add the "Activity results" block to the default region with: | config_showbest | 3 | Then I should see "Please configure this block and select which activity it should display results from." in the "Activity results" "block" Scenario: Try to configure the block on the course page in a course without activities Given I add the "Activity results" block When I configure the "Activity results" block And I should see "There are not yet any activities in this course." And I press "Save changes" Then I should see "Please configure this block and select which activity it should display results from." in the "Activity results" "block" Scenario: Try to configure the block on a resource page in a course without activities Given the following "activity" exists: | activity | page | | course | C1 | | idnumber | 0001 | | name | Test page name | | intro | Test page description | | section | 1 | | content | This is a page | And I am on "Course 1" course homepage When I add the "Activity results" block And I configure the "Activity results" block And I should see "There are not yet any activities in this course." And I press "Save changes" Then I should see "Please configure this block and select which activity it should display results from." in the "Activity results" "block" PK'9\y39activity_results/tests/behat/highscoreswithscales.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays students high scores in group as scales In order to be display student scores as scales As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | And the following "activity" exists: | activity | assign | | course | C1 | | idnumber | 0001 | | name | Test assignment | | intro | Offline text | | assignsubmission_file_enabled | 0 | And the following "scales" exist: | name | scale | | My Scale | Disappointing, Not good enough, Average, Good, Very good, Excellent! | And I am on the "Test assignment" "assign activity editing" page logged in as teacher1 And I set the following fields to these values: | id_grade_modgrade_type | Scale | | id_grade_modgrade_scale | My Scale | And I press "Save and return to course" And I am on the "Course 1" "grades > Grader report > View" page And I turn editing mode on And I give the grade "Excellent!" to the user "Student 1" for the grade item "Test assignment" And I give the grade "Very good" to the user "Student 2" for the grade item "Test assignment" And I give the grade "Good" to the user "Student 3" for the grade item "Test assignment" And I give the grade "Average" to the user "Student 4" for the grade item "Test assignment" And I give the grade "Not good enough" to the user "Student 5" for the grade item "Test assignment" And I press "Save changes" And I am on "Course 1" course homepage with editing mode on Scenario: Configure the block on the course page to show 1 high score Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_nameformat | Display full names | | config_decimalpoints | 0 | Then I should see "Student 1" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using full names Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_nameformat | Display full names | Then I should see "Student 1" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" And I should see "Student 2" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should see "Student 3" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using ID numbers Given the following config values are set as admin: | showuseridentity | idnumber,email | And I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_nameformat | Display only ID numbers | Then I should see "User S1" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" And I should see "User S2" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should see "User S3" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using anonymous names Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_nameformat | Anonymous results | Then I should see "User" in the "Activity results" "block" And I should not see "Student 1" in the "Activity results" "block" And I should see "Excellent!" in the "Activity results" "block" And I should not see "Student 2" in the "Activity results" "block" And I should see "Very good" in the "Activity results" "block" And I should not see "Student 3" in the "Activity results" "block" And I should see "Good" in the "Activity results" "block" PK'9\ 4activity_results/tests/behat/defaultsettings.featurenu[@block @block_activity_results Feature: The activity results block can have administrator set defaults In order to be customize the activity results block As an admin I need can assign some site wide defaults Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "activity" exists: | activity | assign | | course | C1 | | idnumber | 0001 | | name | Test assignment | | assignsubmission_file_enabled | 0 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Assign some site-wide defaults to the block. Given the following config values are set as admin: | config_showbest | 0 | block_activity_results | | config_showworst | 0 | block_activity_results | | config_gradeformat | 2 | block_activity_results | | config_nameformat | 2 | block_activity_results | And I am on "Course 1" course homepage And I add the "Activity results" block When I configure the "Activity results" block And the following fields match these values: | config_showbest | 0 | | config_showworst | 0 | | config_gradeformat | Fractions | | config_nameformat | Display only ID numbers | And I press "Save changes" Then I should see "This block's configuration currently does not allow it to show any results." in the "Activity results" "block" Scenario: Assign some site-wide defaults to the block and lock them. Given the following config values are set as admin: | config_showbest | 0 | block_activity_results | | config_showbest_locked | 1 | block_activity_results | | config_showworst | 0 | block_activity_results | | config_showworst_locked | 1 | block_activity_results | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | activity_results | Course | C1 | course-view-* | side-pre | And I am on "Course 1" course homepage When I configure the "Activity results" block And the following fields match these values: | config_showbest | 0 | | config_showworst | 0 | And the "config_showbest" "field" should be readonly And the "config_showworst" "field" should be readonly And I press "Save changes" Then I should see "This block's configuration currently does not allow it to show any results." in the "Activity results" "block" PK'9\@activity_results/tests/behat/highscoreswithvisiblegroups.featurenu[@block @block_activity_results @javascript Feature: The activity results block displays student in visible groups scores In order to be display student scores As a user I need to see the activity results block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | | student3 | Student | 3 | student3@example.com | S3 | | student4 | Student | 4 | student4@example.com | S4 | | student5 | Student | 5 | student5@example.com | S5 | | student6 | Student | 6 | student6@example.com | S6 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | | Group 2 | C1 | G2 | | Group 3 | C1 | G3 | | Group 4 | C1 | G4 | | Group 5 | C1 | G5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | | student6 | C1 | student | And the following "group members" exist: | user | group | | student1 | G1 | | student2 | G1 | | student3 | G2 | | student4 | G2 | | student5 | G3 | | student6 | G3 | And the following "activities" exist: | activity | name | course | idnumber | section | assignsubmission_file_enabled | groupmode | | assign | Test assignment | C1 | assign1 | 1 | 0 | 2 | And the following "grade grades" exist: | gradeitem | user | grade | | Test assignment | student1 | 100.00 | | Test assignment | student2 | 90.00 | | Test assignment | student3 | 90.00 | | Test assignment | student4 | 80.00 | | Test assignment | student5 | 80.00 | | Test assignment | student6 | 70.00 | And I am on the "Course 1" course page logged in as teacher1 And I turn editing mode on Scenario: Configure the block on the course page to show 1 high score Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | | config_usegroups | Yes | Then I should see "Group 1" in the "Activity results" "block" And I should see "95%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 high score as a fraction Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_gradeformat | Fractions | | config_nameformat | Display full names | | config_usegroups | Yes | And I am on the "Course 1" course page logged in as student1 Then I should see "Group 1" in the "Activity results" "block" And I should see "95.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show 1 high score as a absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 1 | | config_showworst | 0 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group 1" in the "Activity results" "block" And I should see "95.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores as percentages Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display full names | | config_decimalpoints | 0 | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group 1" in the "Activity results" "block" And I should see "95%" in the "Activity results" "block" And I should see "Group 2" in the "Activity results" "block" And I should see "85%" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores as fractions Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Fractions | | config_nameformat | Display full names | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group 1" in the "Activity results" "block" And I should see "95.00/100.00" in the "Activity results" "block" And I should see "Group 2" in the "Activity results" "block" And I should see "85.00/100.00" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75.00/100.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores as absolute numbers Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Absolute numbers | | config_nameformat | Display full names | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group 1" in the "Activity results" "block" And I should see "95.00" in the "Activity results" "block" And I should see "Group 2" in the "Activity results" "block" And I should see "85.00" in the "Activity results" "block" And I should see "Group 3" in the "Activity results" "block" And I should see "75.00" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using ID numbers Given the following config values are set as admin: | showuseridentity | idnumber,email | And I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Display only ID numbers | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group" in the "Activity results" "block" And I should see "95.00%" in the "Activity results" "block" And I should see "85.00%" in the "Activity results" "block" And I should see "75.00%" in the "Activity results" "block" Scenario: Try to configure the block on the course page to show multiple high scores using anonymous names Given I add the "Activity results" block to the default region with: | config_showbest | 3 | | config_showworst | 0 | | config_gradeformat | Percentages | | config_nameformat | Anonymous results | | config_usegroups | Yes | Then I am on the "Course 1" course page logged in as student1 And I should see "Group" in the "Activity results" "block" And I should see "95.00%" in the "Activity results" "block" And I should see "85.00%" in the "Activity results" "block" And I should see "75.00%" in the "Activity results" "block" PK'9\J_+activity_results/block_activity_results.phpnu[. /** * Classes to enforce the various access rules that can apply to a activity. * * @package block_activity_results * @copyright 2009 Tim Hunt * @copyright 2015 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/lib/grade/constants.php'); require_once($CFG->dirroot . '/course/lib.php'); define('B_ACTIVITYRESULTS_NAME_FORMAT_FULL', 1); define('B_ACTIVITYRESULTS_NAME_FORMAT_ID', 2); define('B_ACTIVITYRESULTS_NAME_FORMAT_ANON', 3); define('B_ACTIVITYRESULTS_GRADE_FORMAT_PCT', 1); define('B_ACTIVITYRESULTS_GRADE_FORMAT_FRA', 2); define('B_ACTIVITYRESULTS_GRADE_FORMAT_ABS', 3); define('B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE', 4); /** * Block activity_results class definition. * * This block can be added to a course page or a activity page to display of list of * the best/worst students/groups in a particular activity. * * @package block_activity_results * @copyright 2009 Tim Hunt * @copyright 2015 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_activity_results extends block_base { /** * Core function used to initialize the block. */ public function init() { $this->title = get_string('pluginname', 'block_activity_results'); } /** * Allow the block to have a configuration page * * @return boolean */ public function has_config() { return true; } /** * Core function, specifies where the block can be used. * @return array */ public function applicable_formats() { return array('course-view' => true, 'mod' => true); } /** * If this block belongs to a activity context, then return that activity's id. * Otherwise, return 0. * @return stdclass the activity record. */ public function get_owning_activity() { global $DB; // Set some defaults. $result = new stdClass(); $result->id = 0; if (empty($this->instance->parentcontextid)) { return $result; } $parentcontext = context::instance_by_id($this->instance->parentcontextid); if ($parentcontext->contextlevel != CONTEXT_MODULE) { return $result; } $cm = get_coursemodule_from_id($this->page->cm->modname, $parentcontext->instanceid); if (!$cm) { return $result; } // Get the grade_items id. $rec = $DB->get_record('grade_items', array('iteminstance' => $cm->instance, 'itemmodule' => $this->page->cm->modname)); if (!$rec) { return $result; } // See if it is a gradable activity. if (($rec->gradetype != GRADE_TYPE_VALUE) && ($rec->gradetype != GRADE_TYPE_SCALE)) { return $result; } return $rec; } /** * Used to save the form config data * @param stdclass $data * @param bool $nolongerused */ public function instance_config_save($data, $nolongerused = false) { global $DB; if (empty($data->activitygradeitemid)) { // Figure out info about parent module. $info = $this->get_owning_activity(); $data->activitygradeitemid = $info->id; if ($info->id < 1) { // No activity was selected. $info->itemmodule = ''; $info->iteminstance = ''; } else { $data->activityparent = $info->itemmodule; $data->activityparentid = $info->iteminstance; } } else { // Lookup info about the parent module (we have the id from mdl_grade_items. $info = $DB->get_record('grade_items', array('id' => $data->activitygradeitemid)); $data->activityparent = $info->itemmodule; $data->activityparentid = $info->iteminstance; } parent::instance_config_save($data); } /** * Used to generate the content for the block. * @return string */ public function get_content() { global $USER, $CFG, $DB; if ($this->content !== null) { return $this->content; } $this->content = new stdClass; $this->content->text = ''; $this->content->footer = ''; if (empty($this->instance)) { return $this->content; } // We are configured so use the configuration. if (!empty($this->config->activitygradeitemid)) { // We are configured. $activitygradeitemid = $this->config->activitygradeitemid; // Lookup the module in the grade_items table. $activity = $DB->get_record('grade_items', array('id' => $activitygradeitemid)); if (empty($activity)) { // Activity does not exist. $this->content->text = get_string('error_emptyactivityrecord', 'block_activity_results'); return $this->content; } $courseid = $activity->courseid; $inactivity = false; } else { // Not configured. $activitygradeitemid = 0; } // Check to see if we are in the moule we are displaying results for. if (!empty($this->config->activitygradeitemid)) { if ($this->get_owning_activity()->id == $this->config->activitygradeitemid) { $inactivity = true; } else { $inactivity = false; } } // Activity ID is missing. if (empty($activitygradeitemid)) { $this->content->text = get_string('error_emptyactivityid', 'block_activity_results'); return $this->content; } // Check to see if we are configured. if (empty($this->config->showbest) && empty($this->config->showworst)) { $this->content->text = get_string('configuredtoshownothing', 'block_activity_results'); return $this->content; } // Check to see if it is a supported grade type. if (empty($activity->gradetype) || ($activity->gradetype != GRADE_TYPE_VALUE && $activity->gradetype != GRADE_TYPE_SCALE)) { $this->content->text = get_string('error_unsupportedgradetype', 'block_activity_results'); return $this->content; } // Get the grades for this activity. $sql = 'SELECT * FROM {grade_grades} WHERE itemid = ? AND finalgrade is not NULL ORDER BY finalgrade, timemodified DESC'; $grades = $DB->get_records_sql($sql, array( $activitygradeitemid)); if (empty($grades) || $activity->hidden) { // No grades available, The block will hide itself in this case. return $this->content; } // Set up results. $groupmode = NOGROUPS; $best = array(); $worst = array(); if (!empty($this->config->nameformat)) { $nameformat = $this->config->nameformat; } else { $nameformat = B_ACTIVITYRESULTS_NAME_FORMAT_FULL; } // Get $cm and context. if ($inactivity) { $cm = $this->page->cm; $context = $this->page->context; } else { $cm = get_coursemodule_from_instance($activity->itemmodule, $activity->iteminstance, $courseid); $context = context_module::instance($cm->id); } if (!empty($this->config->usegroups)) { $groupmode = groups_get_activity_groupmode($cm); if ($groupmode == SEPARATEGROUPS && has_capability('moodle/site:accessallgroups', $context)) { // If you have the ability to see all groups then lets show them. $groupmode = VISIBLEGROUPS; } } switch ($groupmode) { case VISIBLEGROUPS: // Display group-mode results. $groups = groups_get_all_groups($courseid); if (empty($groups)) { // No groups exist, sorry. $this->content->text = get_string('error_nogroupsexist', 'block_activity_results'); return $this->content; } // Find out all the userids which have a submitted grade. $userids = array(); $gradeforuser = array(); foreach ($grades as $grade) { $userids[] = $grade->userid; $gradeforuser[$grade->userid] = (float)$grade->finalgrade; } // Now find which groups these users belong in. list($usertest, $params) = $DB->get_in_or_equal($userids); $params[] = $courseid; $usergroups = $DB->get_records_sql(' SELECT gm.id, gm.userid, gm.groupid, g.name FROM {groups} g LEFT JOIN {groups_members} gm ON g.id = gm.groupid WHERE gm.userid ' . $usertest . ' AND g.courseid = ?', $params); // Now, iterate the grades again and sum them up for each group. $groupgrades = array(); foreach ($usergroups as $usergroup) { if (!isset($groupgrades[$usergroup->groupid])) { $groupgrades[$usergroup->groupid] = array( 'sum' => (float)$gradeforuser[$usergroup->userid], 'number' => 1, 'group' => $usergroup->name); } else { $groupgrades[$usergroup->groupid]['sum'] += $gradeforuser[$usergroup->userid]; $groupgrades[$usergroup->groupid]['number'] += 1; } } foreach ($groupgrades as $groupid => $groupgrade) { $groupgrades[$groupid]['average'] = $groupgrades[$groupid]['sum'] / $groupgrades[$groupid]['number']; } // Sort groupgrades according to average grade, ascending. uasort($groupgrades, function($a, $b) { if ($a["average"] == $b["average"]) { return 0; } return ($a["average"] > $b["average"] ? 1 : -1); }); // How many groups do we have with graded member submissions to show? $numbest = empty($this->config->showbest) ? 0 : min($this->config->showbest, count($groupgrades)); $numworst = empty($this->config->showworst) ? 0 : min($this->config->showworst, count($groupgrades) - $numbest); // Collect all the group results we are going to use in $best and $worst. $remaining = $numbest; $groupgrade = end($groupgrades); while ($remaining--) { $best[key($groupgrades)] = $groupgrade['average']; $groupgrade = prev($groupgrades); } $remaining = $numworst; $groupgrade = reset($groupgrades); while ($remaining--) { $worst[key($groupgrades)] = $groupgrade['average']; $groupgrade = next($groupgrades); } // Ready for output! if ($activity->gradetype == GRADE_TYPE_SCALE) { // We must display the results using scales. $gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE; // Preload the scale. $scale = $this->get_scale($activity->scaleid); } else if (intval(empty($this->config->gradeformat))) { $gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_PCT; } else { $gradeformat = $this->config->gradeformat; } // Generate the header. $this->content->text .= $this->activity_link($activity, $cm); if ($nameformat == B_ACTIVITYRESULTS_NAME_FORMAT_FULL) { if (has_capability('moodle/course:managegroups', $context)) { $grouplink = $CFG->wwwroot.'/group/overview.php?id='.$courseid.'&group='; } else if (course_can_view_participants($context)) { $grouplink = $CFG->wwwroot.'/user/index.php?id='.$courseid.'&group='; } else { $grouplink = ''; } } $rank = 0; if (!empty($best)) { $this->content->text .= ''; $this->content->text .= ''; foreach ($best as $groupid => $averagegrade) { switch ($nameformat) { case B_ACTIVITYRESULTS_NAME_FORMAT_ANON: case B_ACTIVITYRESULTS_NAME_FORMAT_ID: $thisname = get_string('group'); break; default: case B_ACTIVITYRESULTS_NAME_FORMAT_FULL: if ($grouplink) { $thisname = ''.$groupgrades[$groupid]['group'].''; } else { $thisname = $groupgrades[$groupid]['group']; } break; } $this->content->text .= ''; } $this->content->text .= '
            '; if ($numbest == 1) { $this->content->text .= get_string('bestgroupgrade', 'block_activity_results'); } else { $this->content->text .= get_string('bestgroupgrades', 'block_activity_results', $numbest); } $this->content->text .= '
            '.(++$rank).'.'.$thisname.''; switch ($gradeformat) { case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE: // Round answer up and locate appropriate scale. $answer = (round($averagegrade, 0, PHP_ROUND_HALF_UP) - 1); if (isset($scale[$answer])) { $this->content->text .= $scale[$answer]; } else { // Value is not in the scale. $this->content->text .= get_string('unknown', 'block_activity_results'); } break; case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA: $this->content->text .= $this->activity_format_grade($averagegrade) . '/' . $this->activity_format_grade($activity->grademax); break; case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS: $this->content->text .= $this->activity_format_grade($averagegrade); break; default: case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT: $this->content->text .= $this->activity_format_grade((float)$averagegrade / (float)$activity->grademax * 100).'%'; break; } $this->content->text .= '
            '; } $rank = 0; if (!empty($worst)) { $worst = array_reverse($worst, true); $this->content->text .= ''; $this->content->text .= ''; foreach ($worst as $groupid => $averagegrade) { switch ($nameformat) { case B_ACTIVITYRESULTS_NAME_FORMAT_ANON: case B_ACTIVITYRESULTS_NAME_FORMAT_ID: $thisname = get_string('group'); break; default: case B_ACTIVITYRESULTS_NAME_FORMAT_FULL: if ($grouplink) { $thisname = ''.$groupgrades[$groupid]['group'].''; } else { $thisname = $groupgrades[$groupid]['group']; } break; } $this->content->text .= ''; } $this->content->text .= '
            '; if ($numworst == 1) { $this->content->text .= get_string('worstgroupgrade', 'block_activity_results'); } else { $this->content->text .= get_string('worstgroupgrades', 'block_activity_results', $numworst); } $this->content->text .= '
            '.(++$rank).'.'.$thisname.''; switch ($gradeformat) { case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE: // Round answer up and locate appropriate scale. $answer = (round($averagegrade, 0, PHP_ROUND_HALF_UP) - 1); if (isset($scale[$answer])) { $this->content->text .= $scale[$answer]; } else { // Value is not in the scale. $this->content->text .= get_string('unknown', 'block_activity_results'); } break; case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA: $this->content->text .= $this->activity_format_grade($averagegrade) . '/' . $this->activity_format_grade($activity->grademax); break; case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS: $this->content->text .= $this->activity_format_grade($averagegrade); break; default: case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT: $this->content->text .= $this->activity_format_grade((float)$averagegrade / (float)$activity->grademax * 100).'%'; break; } $this->content->text .= '
            '; } break; case SEPARATEGROUPS: // This is going to be just like no-groups mode, only we 'll filter // out the grades from people not in our group. if (!isloggedin()) { // Not logged in, so show nothing. return $this->content; } $mygroups = groups_get_all_groups($courseid, $USER->id); if (empty($mygroups)) { // Not member of a group, show nothing. return $this->content; } // Get users from the same groups as me. list($grouptest, $params) = $DB->get_in_or_equal(array_keys($mygroups)); $mygroupsusers = $DB->get_records_sql_menu( 'SELECT DISTINCT userid, 1 FROM {groups_members} WHERE groupid ' . $grouptest, $params); // Filter out the grades belonging to other users, and proceed as if there were no groups. foreach ($grades as $key => $grade) { if (!isset($mygroupsusers[$grade->userid])) { unset($grades[$key]); } } // No break, fall through to the default case now we have filtered the $grades array. default: case NOGROUPS: // Single user mode. $numbest = empty($this->config->showbest) ? 0 : min($this->config->showbest, count($grades)); $numworst = empty($this->config->showworst) ? 0 : min($this->config->showworst, count($grades) - $numbest); // Collect all the usernames we are going to need. $remaining = $numbest; $grade = end($grades); while ($remaining--) { $best[$grade->userid] = $grade->id; $grade = prev($grades); } $remaining = $numworst; $grade = reset($grades); while ($remaining--) { $worst[$grade->userid] = $grade->id; $grade = next($grades); } if (empty($best) && empty($worst)) { // Nothing to show, for some reason... return $this->content; } // Now grab all the users from the database. $userids = array_merge(array_keys($best), array_keys($worst)); $fields = array_merge(array('id', 'idnumber'), \core_user\fields::get_name_fields()); $fields = implode(',', $fields); $users = $DB->get_records_list('user', 'id', $userids, '', $fields); // If configured to view user idnumber, ensure current user can see it. $extrafields = \core_user\fields::for_identity($this->context)->get_required_fields(); $canviewidnumber = (array_search('idnumber', $extrafields) !== false); // Ready for output! if ($activity->gradetype == GRADE_TYPE_SCALE) { // We must display the results using scales. $gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE; // Preload the scale. $scale = $this->get_scale($activity->scaleid); } else if (intval(empty($this->config->gradeformat))) { $gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_PCT; } else { $gradeformat = $this->config->gradeformat; } // Generate the header. $this->content->text .= $this->activity_link($activity, $cm); $rank = 0; if (!empty($best)) { $this->content->text .= ''; $this->content->text .= ''; foreach ($best as $userid => $gradeid) { switch ($nameformat) { case B_ACTIVITYRESULTS_NAME_FORMAT_ID: $thisname = get_string('user'); if ($canviewidnumber) { $thisname .= ' ' . s($users[$userid]->idnumber); } break; case B_ACTIVITYRESULTS_NAME_FORMAT_ANON: $thisname = get_string('user'); break; default: case B_ACTIVITYRESULTS_NAME_FORMAT_FULL: if (has_capability('moodle/user:viewdetails', $context)) { $thisname = html_writer::link(new moodle_url('/user/view.php', array('id' => $userid, 'course' => $courseid)), fullname($users[$userid])); } else { $thisname = fullname($users[$userid]); } break; } $this->content->text .= ''; } $this->content->text .= '
            '; if ($numbest == 1) { $this->content->text .= get_string('bestgrade', 'block_activity_results'); } else { $this->content->text .= get_string('bestgrades', 'block_activity_results', $numbest); } $this->content->text .= '
            '.(++$rank).'.'.$thisname.''; switch ($gradeformat) { case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE: // Round answer up and locate appropriate scale. $answer = (round($grades[$gradeid]->finalgrade, 0, PHP_ROUND_HALF_UP) - 1); if (isset($scale[$answer])) { $this->content->text .= $scale[$answer]; } else { // Value is not in the scale. $this->content->text .= get_string('unknown', 'block_activity_results'); } break; case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA: $this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade); $this->content->text .= '/'.$this->activity_format_grade($activity->grademax); break; case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS: $this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade); break; default: case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT: if ($activity->grademax) { $this->content->text .= $this->activity_format_grade((float)$grades[$gradeid]->finalgrade / (float)$activity->grademax * 100).'%'; } else { $this->content->text .= '--%'; } break; } $this->content->text .= '
            '; } $rank = 0; if (!empty($worst)) { $worst = array_reverse($worst, true); $this->content->text .= ''; $this->content->text .= ''; foreach ($worst as $userid => $gradeid) { switch ($nameformat) { case B_ACTIVITYRESULTS_NAME_FORMAT_ID: $thisname = get_string('user'); if ($canviewidnumber) { $thisname .= ' ' . s($users[$userid]->idnumber); }; break; case B_ACTIVITYRESULTS_NAME_FORMAT_ANON: $thisname = get_string('user'); break; default: case B_ACTIVITYRESULTS_NAME_FORMAT_FULL: if (has_capability('moodle/user:viewdetails', $context)) { $thisname = html_writer::link(new moodle_url('/user/view.php', array('id' => $userid, 'course' => $courseid)), fullname($users[$userid])); } else { $thisname = fullname($users[$userid]); } break; } $this->content->text .= ''; } $this->content->text .= '
            '; if ($numbest == 1) { $this->content->text .= get_string('worstgrade', 'block_activity_results'); } else { $this->content->text .= get_string('worstgrades', 'block_activity_results', $numworst); } $this->content->text .= '
            '.(++$rank).'.'.$thisname.''; switch ($gradeformat) { case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE: // Round answer up and locate appropriate scale. $answer = (round($grades[$gradeid]->finalgrade, 0, PHP_ROUND_HALF_UP) - 1); if (isset($scale[$answer])) { $this->content->text .= $scale[$answer]; } else { // Value is not in the scale. $this->content->text .= get_string('unknown', 'block_activity_results'); } break; case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA: $this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade); $this->content->text .= '/'.$this->activity_format_grade($activity->grademax); break; case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS: $this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade); break; default: case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT: if ($activity->grademax) { $this->content->text .= $this->activity_format_grade((float)$grades[$gradeid]->finalgrade / (float)$activity->grademax * 100).'%'; } else { $this->content->text .= '--%'; } break; } $this->content->text .= '
            '; } break; } return $this->content; } /** * Allows the block to be added multiple times to a single page * @return boolean */ public function instance_allow_multiple() { return true; } /** * Formats the grade to the specified decimal points * @param float $grade * @return string */ private function activity_format_grade($grade) { if (is_null($grade)) { return get_string('notyetgraded', 'block_activity_results'); } return format_float($grade, $this->config->decimalpoints); } /** * Generates the Link to the activity module when displayed outside of the module. * @param stdclass $activity * @param stdclass $cm * @return string */ private function activity_link($activity, $cm) { $o = html_writer::start_tag('h5'); $o .= html_writer::link(new moodle_url('/mod/'.$activity->itemmodule.'/view.php', array('id' => $cm->id)), format_string(($activity->itemname), true, ['context' => context_module::instance($cm->id)])); $o .= html_writer::end_tag('h5'); return $o; } /** * Generates a numeric array of scale entries * @param int $scaleid * @return array */ private function get_scale($scaleid) { global $DB; $scaletext = $DB->get_field('scale', 'scale', array('id' => $scaleid), IGNORE_MISSING); $scale = explode ( ',', $scaletext); return $scale; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $instanceconfigs = !empty($this->config) ? $this->config : new stdClass(); $pluginconfigs = get_config('block_activity_results'); return (object) [ 'instance' => $instanceconfigs, 'plugin' => $pluginconfigs, ]; } } PK'9\ Ѳactivity_results/settings.phpnu[. /** * Defines the form for editing activity results block instances. * * @package block_activity_results * @copyright 2016 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { // Default high scores. $setting = new admin_setting_configtext('block_activity_results/config_showbest', new lang_string('defaulthighestgrades', 'block_activity_results'), new lang_string('defaulthighestgrades_desc', 'block_activity_results'), 3, PARAM_INT); $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false); $settings->add($setting); // Default low scores. $setting = new admin_setting_configtext('block_activity_results/config_showworst', new lang_string('defaultlowestgrades', 'block_activity_results'), new lang_string('defaultlowestgrades_desc', 'block_activity_results'), 0, PARAM_INT); $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false); $settings->add($setting); // Default group display. $yesno = array(0 => get_string('no'), 1 => get_string('yes')); $setting = new admin_setting_configselect('block_activity_results/config_usegroups', new lang_string('defaultshowgroups', 'block_activity_results'), new lang_string('defaultshowgroups_desc', 'block_activity_results'), 0, $yesno); $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false); $settings->add($setting); // Default privacy settings. $nameoptions = array( B_ACTIVITYRESULTS_NAME_FORMAT_FULL => get_string('config_names_full', 'block_activity_results'), B_ACTIVITYRESULTS_NAME_FORMAT_ID => get_string('config_names_id', 'block_activity_results'), B_ACTIVITYRESULTS_NAME_FORMAT_ANON => get_string('config_names_anon', 'block_activity_results') ); $setting = new admin_setting_configselect('block_activity_results/config_nameformat', new lang_string('defaultnameoptions', 'block_activity_results'), new lang_string('defaultnameoptions_desc', 'block_activity_results'), B_ACTIVITYRESULTS_NAME_FORMAT_FULL, $nameoptions); $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false); $settings->add($setting); // Default grade display settings. $gradeoptions = array( B_ACTIVITYRESULTS_GRADE_FORMAT_PCT => get_string('config_format_percentage', 'block_activity_results'), B_ACTIVITYRESULTS_GRADE_FORMAT_FRA => get_string('config_format_fraction', 'block_activity_results'), B_ACTIVITYRESULTS_GRADE_FORMAT_ABS => get_string('config_format_absolute', 'block_activity_results') ); $setting = new admin_setting_configselect('block_activity_results/config_gradeformat', new lang_string('defaultgradedisplay', 'block_activity_results'), new lang_string('defaultgradedisplay_desc', 'block_activity_results'), B_ACTIVITYRESULTS_GRADE_FORMAT_PCT, $gradeoptions); $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false); $settings->add($setting); // Default decimal places. $places = array(); for ($i = 0; $i <= 5; $i++) { $places[$i] = $i; } $setting = new admin_setting_configselect('block_activity_results/config_decimalpoints', new lang_string('defaultdecimalplaces', 'block_activity_results'), new lang_string('defaultdecimalplaces_desc', 'block_activity_results'), 2, $places); $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false); $settings->add($setting); } PK'9\[T rss_client/viewfeed.phpnu[. /** * Script to let a user view the output of a particular RSS feed. * * @package block_rss_client * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../../config.php'); require_once($CFG->libdir .'/simplepie/moodle_simplepie.php'); require_login(); $returnurl = optional_param('returnurl', '', PARAM_LOCALURL); $courseid = optional_param('courseid', 0, PARAM_INT); $rssid = required_param('rssid', PARAM_INT); if ($courseid = SITEID) { $courseid = 0; } if ($courseid) { $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); $PAGE->set_course($course); $context = $PAGE->context; } else { $context = context_system::instance(); $PAGE->set_context($context); } $managesharedfeeds = has_capability('block/rss_client:manageanyfeeds', $context); if (!$managesharedfeeds) { require_capability('block/rss_client:manageownfeeds', $context); } $urlparams = array('rssid' => $rssid); if ($courseid) { $urlparams['courseid'] = $courseid; } if ($returnurl) { $urlparams['returnurl'] = $returnurl; } $PAGE->set_url('/blocks/rss_client/viewfeed.php', $urlparams); $PAGE->set_pagelayout('popup'); if ($managesharedfeeds) { $select = 'id = :id AND (userid = :userid OR shared = 1)'; } else { $select = 'id = :id AND userid = :userid'; } $rssrecord = $DB->get_record_select('block_rss_client', $select, [ 'id' => $rssid, 'userid' => $USER->id, ], '*', MUST_EXIST); $rss = new moodle_simplepie($rssrecord->url); if ($rss->error()) { debugging($rss->error()); throw new \moodle_exception('errorfetchingrssfeed'); } $strviewfeed = get_string('viewfeed', 'block_rss_client'); $PAGE->set_title($strviewfeed); $PAGE->set_heading($strviewfeed); $managefeeds = new moodle_url('/blocks/rss_client/managefeeds.php', $urlparams); $PAGE->navbar->add(get_string('blocks')); $PAGE->navbar->add(get_string('pluginname', 'block_rss_client')); $PAGE->navbar->add(get_string('managefeeds', 'block_rss_client'), $managefeeds); $PAGE->navbar->add($strviewfeed); echo $OUTPUT->header(); if (!empty($rssrecord->preferredtitle)) { $feedtitle = $rssrecord->preferredtitle; } else { $feedtitle = $rss->get_title(); } echo '' . "\n"; echo ''."\n"; foreach ($rss->get_items() as $item) { echo ''."\n"; echo ''."\n"; echo ''."\n"; } echo '
            '. s($feedtitle) .'
            '."\n"; echo ''; echo s($item->get_title()); echo ''."\n"; echo '
            '; echo format_text($item->get_description(), FORMAT_HTML) .'
            '."\n"; echo $OUTPUT->footer(); PK'9\}1rss_client/styles.cssnu[/* RSS Feeds -------------------------*/ .block_rss_client .list li:first-child { border-top-width: 0; } .block_rss_client .list li { border-top: 1px solid; padding: 5px; }PK'9\V-Ejjrss_client/db/install.phpnu[. /** * RSS client block installation. * * @package block_rss_client * @copyright 2021 Amaia Anabitarte * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Perform the post-install procedures. */ function xmldb_block_rss_client_install() { global $DB; // Disable rss_client on new installs by default. $DB->set_field('block', 'visible', 0, ['name' => 'rss_client']); } PK'9\rss_client/db/tasks.phpnu[. /** * Task definition for block_rss_client. * @author Farhan Karmali * @copyright Farhan Karmali 2018 * @package block_rss_client * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $tasks = array( array( 'classname' => '\block_rss_client\task\refreshfeeds', 'blocking' => 0, 'minute' => '*/5', 'hour' => '*', 'day' => '*', 'month' => '*', 'dayofweek' => '*', 'disabled' => 0 ) ); PK'9\%!У==rss_client/db/access.phpnu[. /** * RSS client block caps. * * @package block_rss_client * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/rss_client:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/rss_client:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), 'block/rss_client:manageownfeeds' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ) ), 'block/rss_client:manageanyfeeds' => array( 'riskbitmask' => RISK_SPAM, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'manager' => CAP_ALLOW ) ) ); PK'9\^'rss_client/db/upgrade.phpnu[. /** * Database upgrades for the RSS block. * * @package block_rss_client * @copyright 2014 Davo Smith * @author Neill Magill * @license http://www.gnu.org/copyleft/gpl.html GNU GPL */ /** * Upgrade the block_rss_client database. * * @param int $oldversion The version number of the plugin that was installed. * @return boolean */ function xmldb_block_rss_client_upgrade($oldversion) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\e0=rss_client/db/install.xmlnu[
            PK'9\J'rss_client/lang/en/block_rss_client.phpnu[. /** * Strings for component 'block_rss_client', language 'en', branch 'MOODLE_20_STABLE' * * @package block_rss_client * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['addfeed'] = 'Add RSS feed URL'; $string['addheadlineblock'] = 'Add RSS headline block'; $string['addnew'] = 'Add new'; $string['addnewfeed'] = 'Add a new feed'; $string['cannotmakemodification'] = 'You are not allowed to make modifications to this RSS feed at this time.'; $string['choosefeedlabel'] = 'Select feeds to display'; $string['clientchannellink'] = 'Source site...'; $string['clientnumentries'] = 'The default number of entries to show per feed.'; $string['clientshowchannellinklabel'] = 'Show source link if available'; $string['clientshowimagelabel'] = 'Show channel image if available'; $string['configblock'] = 'Configure this block'; $string['configmethodexisting'] = 'Select existing RSS feed'; $string['configmethodnew'] = 'Add new RSS feed'; $string['couldnotfindfeed'] = 'Could not find feed with ID.'; $string['couldnotfindloadrssfeed'] = 'Could not find or load the RSS feed.'; $string['customtitlelabel'] = 'Custom title (leave blank to use title supplied by feed)'; $string['deletefeedconfirm'] = 'Are you sure you want to delete this feed?'; $string['disabledrssfeeds'] = 'RSS feeds are disabled'; $string['displaydescriptionlabel'] = 'Show descriptions for entries'; $string['displaysettings'] = 'Display settings for RSS feed'; $string['editafeed'] = 'Edit a feed'; $string['editfeeds'] = 'Edit, subscribe or unsubscribe from RSS feeds'; $string['editnewsfeeds'] = 'Edit news feeds'; $string['editrssblock'] = 'Edit RSS headline block'; $string['enableautodiscovery'] = 'Enable auto-discovery of feeds?'; $string['enableautodiscovery_help'] = 'If enabled, feeds on web pages are found automatically. For example, if https://docs.moodle.org is entered, then https://docs.moodle.org/en/index.php?title=Special:RecentChanges&feed=rss would be found.'; $string['failedfeed'] = 'Feed failed to download - will retry after {$a}'; $string['failedfeeds'] = 'One or more RSS feeds have failed'; $string['feed'] = 'Feed'; $string['feedadded'] = 'RSS feed added'; $string['feeddeleted'] = 'RSS feed deleted'; $string['feeds'] = 'RSS feeds'; $string['feedsaddedit'] = 'Add/edit feeds'; $string['feedsconfigurenewinstance'] = 'Click here to configure this block to display RSS feeds.'; $string['feedsconfigurenewinstance2'] = 'Click the edit icon above to configure this block to display RSS feeds.'; $string['feedupdated'] = 'RSS feed updated'; $string['feedurl'] = 'RSS link'; $string['findmorefeeds'] = 'Find more RSS feeds'; $string['managefeeds'] = 'Manage RSS feeds'; $string['nofeeds'] = 'There are not yet any RSS feeds. Choose \'Add new RSS feed\'.'; $string['numentries'] = 'Entries per feed'; $string['pickfeed'] = 'Pick a RSS feed'; $string['pluginname'] = 'RSS feeds'; $string['privacy:metadata:block_rss_client:description'] = 'The description of the RSS feed.'; $string['privacy:metadata:block_rss_client:preferredtitle'] = 'The preferred (custom) title of the RSS feed.'; $string['privacy:metadata:block_rss_client:shared'] = 'If the RSS feed is available to all courses.'; $string['privacy:metadata:block_rss_client:skiptime'] = 'The defined time in seconds that the cron will wait between attempts to retry failing RSS feeds.'; $string['privacy:metadata:block_rss_client:skipuntil'] = 'The maximum defined time that the cron will attempt to open failing RSS feeds.'; $string['privacy:metadata:block_rss_client:tableexplanation'] = 'RSS block information is stored here.'; $string['privacy:metadata:block_rss_client:title'] = 'The title of the RSS feed.'; $string['privacy:metadata:block_rss_client:url'] = 'The URL of the RSS feed.'; $string['privacy:metadata:block_rss_client:userid'] = 'The ID of the user that added the RSS feed.'; $string['remotenewsfeed'] = 'RSS feed'; $string['refreshfeedstask'] = 'Refresh RSS feeds task'; $string['rss_client:addinstance'] = 'Add new RSS feeds block'; $string['rss_client:createprivatefeeds'] = 'Create private RSS feeds'; $string['rss_client:createsharedfeeds'] = 'Create shared RSS feeds'; $string['rss_client:manageanyfeeds'] = 'Manage any RSS feeds'; $string['rss_client:manageownfeeds'] = 'Manage own RSS feeds'; $string['rss_client:myaddinstance'] = 'Add new RSS feeds block to Dashboard'; $string['seeallfeeds'] = 'See all feeds'; $string['sharedfeed'] = 'Shared feed'; $string['shownumentrieslabel'] = 'Entries to display'; $string['submitters'] = 'Who will be allowed to define new RSS feeds? Defined feeds are available for any page on your site.'; $string['submitters2'] = 'Submitters'; $string['timeout'] = 'Time in minutes before an RSS feed expires in cache. Note that this time defines the minimum time before expiry; the feed will be refreshed in cache on the next cron execution after expiry. Recommended values are 30 mins or greater.'; $string['timeoutdesc'] = 'Time in minutes for an RSS feed to live in cache.'; $string['timeout2'] = 'Timeout'; $string['updatefeed'] = 'Update RSS feed URL'; $string['viewfeed'] = 'View feed'; PK'9\Ktx x 9rss_client/backup/moodle2/restore_rss_client_stepslib.phpnu[. /** * @package block_rss_client * @subpackage backup-moodle2 * @copyright 2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Define all the restore steps that wll be used by the restore_rss_client_block_task */ /** * Define the complete rss_client structure for restore */ class restore_rss_client_block_structure_step extends restore_structure_step { protected function define_structure() { $paths = array(); $paths[] = new restore_path_element('block', '/block', true); $paths[] = new restore_path_element('rss_client', '/block/rss_client'); $paths[] = new restore_path_element('feed', '/block/rss_client/feeds/feed'); return $paths; } public function process_block($data) { global $DB; $data = (object)$data; $feedsarr = array(); // To accumulate feeds // For any reason (non multiple, dupe detected...) block not restored, return if (!$this->task->get_blockid()) { return; } // Iterate over all the feed elements, creating them if needed if (isset($data->rss_client['feeds']['feed'])) { foreach ($data->rss_client['feeds']['feed'] as $feed) { $feed = (object)$feed; // Look if the same feed is available by url and (shared or userid) $select = 'url = :url AND (shared = 1 OR userid = :userid)'; $params = array('url' => $feed->url, 'userid' => $this->task->get_userid()); // The feed already exists, use it if ($feedid = $DB->get_field_select('block_rss_client', 'id', $select, $params, IGNORE_MULTIPLE)) { $feedsarr[] = $feedid; // The feed doesn't exist, create it } else { $feed->userid = $this->task->get_userid(); $feedid = $DB->insert_record('block_rss_client', $feed); $feedsarr[] = $feedid; } } } // Adjust the serialized configdata->rssid to the created/mapped feeds // Get the configdata $configdata = $DB->get_field('block_instances', 'configdata', array('id' => $this->task->get_blockid())); // Extract configdata $config = unserialize_object(base64_decode($configdata)); // Set array of used rss feeds $config->rssid = $feedsarr; // Serialize back the configdata $configdata = base64_encode(serialize($config)); // Set the configdata back $DB->set_field('block_instances', 'configdata', $configdata, array('id' => $this->task->get_blockid())); } } PK'9\WWArss_client/backup/moodle2/restore_rss_client_block_task.class.phpnu[. /** * @package block_rss_client * @subpackage backup-moodle2 * @copyright 2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once($CFG->dirroot . '/blocks/rss_client/backup/moodle2/restore_rss_client_stepslib.php'); // We have structure steps /** * Specialised restore task for the rss_client block * (has own DB structures to backup) * * TODO: Finish phpdocs */ class restore_rss_client_block_task extends restore_block_task { protected function define_my_settings() { } protected function define_my_steps() { // rss_client has one structure step $this->add_step(new restore_rss_client_block_structure_step('rss_client_structure', 'rss_client.xml')); } public function get_fileareas() { return array(); // No associated fileareas } public function get_configdata_encoded_attributes() { return array(); // No special handling of configdata } public static function define_decode_contents() { return array(); } public static function define_decode_rules() { return array(); } } PK'9\W''@rss_client/backup/moodle2/backup_rss_client_block_task.class.phpnu[. /** * @package block_rss_client * @subpackage backup-moodle2 * @copyright 2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once($CFG->dirroot . '/blocks/rss_client/backup/moodle2/backup_rss_client_stepslib.php'); // We have structure steps /** * Specialised backup task for the rss_client block * (has own DB structures to backup) * * TODO: Finish phpdocs */ class backup_rss_client_block_task extends backup_block_task { protected function define_my_settings() { } protected function define_my_steps() { // rss_client has one structure step $this->add_step(new backup_rss_client_block_structure_step('rss_client_structure', 'rss_client.xml')); } public function get_fileareas() { return array(); // No associated fileareas } public function get_configdata_encoded_attributes() { return array(); // No special handling of configdata } public static function encode_content_links($content) { return $content; // No special encoding of links } } PK'9\.UD D 8rss_client/backup/moodle2/backup_rss_client_stepslib.phpnu[. /** * @package block_rss_client * @subpackage backup-moodle2 * @copyright 2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Define all the backup steps that wll be used by the backup_rss_client_block_task */ /** * Define the complete forum structure for backup, with file and id annotations */ class backup_rss_client_block_structure_step extends backup_block_structure_step { protected function define_structure() { global $DB; // Get the block $block = $DB->get_record('block_instances', array('id' => $this->task->get_blockid())); // Extract configdata $config = unserialize_object(base64_decode($block->configdata)); // Get array of used rss feeds if (!empty($config->rssid)) { $feedids = $config->rssid; // Get the IN corresponding query list($in_sql, $in_params) = $DB->get_in_or_equal($feedids); // Define all the in_params as sqlparams foreach ($in_params as $key => $value) { $in_params[$key] = backup_helper::is_sqlparam($value); } } // Define each element separated $rss_client = new backup_nested_element('rss_client', array('id'), null); $feeds = new backup_nested_element('feeds'); $feed = new backup_nested_element('feed', array('id'), array( 'title', 'preferredtitle', 'description', 'shared', 'url')); // Build the tree $rss_client->add_child($feeds); $feeds->add_child($feed); // Define sources $rss_client->set_source_array(array((object)array('id' => $this->task->get_blockid()))); // Only if there are feeds if (!empty($config->rssid)) { $feed->set_source_sql(" SELECT * FROM {block_rss_client} WHERE id $in_sql", $in_params); } // Annotations (none) // Return the root element (rss_client), wrapped into standard block structure return $this->prepare_block_structure($rss_client); } } PK'9\B!rss_client/backup/moodle1/lib.phpnu[. /** * Provides support for the conversion of moodle1 backup to the moodle2 format * * @package block_rss_client * @copyright 2012 Paul Nicholls * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Block conversion handler for rss_client */ class moodle1_block_rss_client_handler extends moodle1_block_handler { public function process_block(array $data) { parent::process_block($data); $instanceid = $data['id']; $contextid = $this->converter->get_contextid(CONTEXT_BLOCK, $data['id']); // Moodle 1.9 backups do not include sufficient data to restore feeds, so we need an empty shell rss_client.xml // for the restore process to find $this->open_xml_writer("course/blocks/{$data['name']}_{$instanceid}/rss_client.xml"); $this->xmlwriter->begin_tag('block', array('id' => $instanceid, 'contextid' => $contextid, 'blockname' => 'rss_client')); $this->xmlwriter->begin_tag('rss_client', array('id' => $instanceid)); $this->xmlwriter->full_tag('feeds', ''); $this->xmlwriter->end_tag('rss_client'); $this->xmlwriter->end_tag('block'); $this->close_xml_writer(); return $data; } } PK'9\b$$rss_client/edit_form.phpnu[. /** * Form for editing RSS client block instances. * * @package block_rss_client * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir .'/simplepie/moodle_simplepie.php'); /** * Form for editing RSS client block instances. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_rss_client_edit_form extends block_edit_form { /** @var stdClass|null The new RSS feed URL object. */ private ?stdClass $newrss = null; protected function specific_definition($mform) { global $CFG, $DB, $USER; // Fields for editing block contents. $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $radiogroup = [ $mform->createElement('radio', 'config_method', get_string('configmethodexisting', 'block_rss_client'), null, 'existing'), $mform->createElement('radio', 'config_method', get_string('configmethodnew', 'block_rss_client'), null, 'new'), ]; $mform->addGroup( elements: $radiogroup, name: 'config_method_group', separator: ['   '], appendName: false, ); $mform->setDefault('config_method', 'existing'); // Add new RSS feed. $mform->addElement('text', 'config_feedurl', get_string('feedurl', 'block_rss_client')); $mform->setType('config_feedurl', PARAM_URL); $mform->hideIf('config_feedurl', 'config_method', 'ne', 'new'); // Select existing RSS feed. $insql = ''; $params = array('userid' => $USER->id); if (!empty($this->block->config) && !empty($this->block->config->rssid)) { list($insql, $inparams) = $DB->get_in_or_equal($this->block->config->rssid, SQL_PARAMS_NAMED); $insql = "OR id $insql "; $params += $inparams; } $titlesql = "CASE WHEN {$DB->sql_isempty('block_rss_client','preferredtitle', false, false)} THEN {$DB->sql_compare_text('title', 64)} ELSE preferredtitle END"; $rssfeeds = $DB->get_records_sql_menu(" SELECT id, $titlesql FROM {block_rss_client} WHERE userid = :userid OR shared = 1 $insql ORDER BY $titlesql", $params); if ($rssfeeds) { $select = $mform->addElement('select', 'config_rssid', get_string('choosefeedlabel', 'block_rss_client'), $rssfeeds); $select->setMultiple(true); $mform->hideIf('config_rssid', 'config_method', 'ne', 'existing'); } else { $mform->addElement('static', 'config_rssid_no_feeds', get_string('choosefeedlabel', 'block_rss_client'), get_string('nofeeds', 'block_rss_client')); $mform->hideIf('config_rssid_no_feeds', 'config_method', 'ne', 'existing'); } // Subheading: Display settings for RSS feed. $startsubheading = '

            '; $endsubheading = '

            '; $mform->addElement('html', $startsubheading . get_string('displaysettings', 'block_rss_client') . $endsubheading); $mform->addElement('text', 'config_title', get_string('uploadlabel')); $mform->setType('config_title', PARAM_NOTAGS); $mform->addElement('selectyesno', 'config_display_description', get_string('displaydescriptionlabel', 'block_rss_client')); $mform->setDefault('config_display_description', 0); $mform->addElement('text', 'config_shownumentries', get_string('shownumentrieslabel', 'block_rss_client'), ['size' => 5]); $mform->setType('config_shownumentries', PARAM_INT); $mform->addRule('config_shownumentries', null, 'numeric', null, 'client'); if (!empty($CFG->block_rss_client_num_entries)) { $mform->setDefault('config_shownumentries', $CFG->block_rss_client_num_entries); } else { $mform->setDefault('config_shownumentries', 5); } $mform->addElement('selectyesno', 'config_block_rss_client_show_channel_link', get_string('clientshowchannellinklabel', 'block_rss_client')); $mform->setDefault('config_block_rss_client_show_channel_link', 0); $mform->addElement('selectyesno', 'config_block_rss_client_show_channel_image', get_string('clientshowimagelabel', 'block_rss_client')); $mform->setDefault('config_block_rss_client_show_channel_image', 0); } /** * Overriding the get_data function to insert a new RSS ID. */ public function get_data(): ?stdClass { $data = parent::get_data(); // Force the 'existing` method as a default. $data->config_method = 'existing'; // Sanitize the title to prevent XSS (Cross-Site Scripting) attacks by encoding special characters into HTML entities. $data->config_title = htmlspecialchars($data->config_title, ENT_QUOTES, 'utf-8'); // If the new RSS is not empty then add the ID to the config_rssid. if ($data && $this->newrss) { $data->config_rssid[] = $this->newrss->id; } return $data; } /** * Overriding the definition_after_data to empty the input. */ public function definition_after_data(): void { parent::definition_after_data(); $mform =& $this->_form; // If form is not submitted then empty the feed URL. if (!$this->is_submitted()) { $mform->getElement('config_feedurl')->setValue(''); } } /** * Overriding the validation to validate the RSS URL and store it to the database. * * If there are no errors, insert the new feed to the database and store the object in * the private property so it can be saved to the RSS block config. * * @param array $data Data from the form. * @param array $files Files form the form. * @return array of errors from validation. */ public function validation($data, $files): array { global $USER, $DB; $errors = parent::validation($data, $files); if ($data['config_method'] === "new") { // If the "New" method is selected and the feed URL is not empty, then proceed. if ($data['config_feedurl']) { if (!filter_var($data['config_feedurl'], FILTER_VALIDATE_URL)) { $errors['config_feedurl'] = get_string('couldnotfindloadrssfeed', 'block_rss_client'); return $errors; } try { $rss = new moodle_simplepie(); // Set timeout for longer than normal to try and grab the feed. $rss->set_timeout(10); $rss->set_feed_url($data['config_feedurl']); $rss->set_autodiscovery_cache_duration(0); $rss->set_autodiscovery_level(moodle_simplepie::LOCATOR_ALL); $rss->init(); if ($rss->error()) { $errors['config_feedurl'] = get_string('couldnotfindloadrssfeed', 'block_rss_client'); } else { // Return URL without quoting. $discoveredurl = new moodle_url($rss->subscribe_url()); $theurl = $discoveredurl->out(false); // Save the RSS to the database. $this->newrss = new stdClass; $this->newrss->userid = $USER->id; $this->newrss->title = $rss->get_title(); $this->newrss->description = $rss->get_description(); $this->newrss->url = $theurl; $newrssid = $DB->insert_record('block_rss_client', $this->newrss); $this->newrss->id = $newrssid; } } catch (Exception $e) { $errors['config_feedurl'] = get_string('couldnotfindloadrssfeed', 'block_rss_client'); } } else { // If the "New" method is selected, but the feed URL is empty, then raise error. $errors['config_feedurl'] = get_string('err_required', 'form'); } } return $errors; } /** * Display the configuration form when block is being added to the page * * @return bool */ public static function display_form_when_adding(): bool { return true; } } PK'9\mT rss_client/version.phpnu[. /** * Version details * * @package block_rss_client * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_rss_client'; // Full name of the plugin (used for diagnostics) PK'9\~rss_client/managefeeds.phpnu[. /** * Script to let a user manage their RSS feeds. * * @package block_rss_client * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../../config.php'); require_once($CFG->libdir . '/tablelib.php'); require_login(); $returnurl = optional_param('returnurl', '', PARAM_LOCALURL); $courseid = optional_param('courseid', 0, PARAM_INT); $deleterssid = optional_param('deleterssid', 0, PARAM_INT); if ($courseid == SITEID) { $courseid = 0; } if ($courseid) { $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); $PAGE->set_course($course); $context = $PAGE->context; } else { $context = context_system::instance(); $PAGE->set_context($context); } $managesharedfeeds = has_capability('block/rss_client:manageanyfeeds', $context); if (!$managesharedfeeds) { require_capability('block/rss_client:manageownfeeds', $context); } $urlparams = array(); $extraparams = ''; if ($courseid) { $urlparams['courseid'] = $courseid; $extraparams = '&courseid=' . $courseid; } if ($returnurl) { $urlparams['returnurl'] = $returnurl; $extraparams = '&returnurl=' . $returnurl; } $baseurl = new moodle_url('/blocks/rss_client/managefeeds.php', $urlparams); $PAGE->set_url($baseurl); if ($managesharedfeeds) { $select = '(userid = :userid OR shared = 1)'; } else { $select = 'userid = :userid'; } // Process any actions if ($deleterssid && confirm_sesskey()) { $deleterssid = $DB->get_field_select('block_rss_client', 'id', "id = :id AND {$select}", [ 'id' => $deleterssid, 'userid' => $USER->id ], MUST_EXIST); $DB->delete_records('block_rss_client', ['id' => $deleterssid]); redirect($PAGE->url, get_string('feeddeleted', 'block_rss_client')); } // Display the list of feeds. $feeds = $DB->get_records_select('block_rss_client', $select, ['userid' => $USER->id], $DB->sql_order_by_text('title')); $strmanage = get_string('managefeeds', 'block_rss_client'); $PAGE->set_pagelayout('standard'); $PAGE->set_title($strmanage); $PAGE->set_heading($strmanage); $managefeeds = new moodle_url('/blocks/rss_client/managefeeds.php', $urlparams); $PAGE->navbar->add(get_string('blocks')); $PAGE->navbar->add(get_string('pluginname', 'block_rss_client')); $PAGE->navbar->add(get_string('managefeeds', 'block_rss_client'), $managefeeds); echo $OUTPUT->header(); $table = new flexible_table('rss-display-feeds'); $table->define_columns(array('feed', 'actions')); $table->define_headers(array(get_string('feed', 'block_rss_client'), get_string('actions', 'moodle'))); $table->define_baseurl($baseurl); $table->set_attribute('cellspacing', '0'); $table->set_attribute('id', 'rssfeeds'); $table->set_attribute('class', 'generaltable generalbox'); $table->column_class('feed', 'feed'); $table->column_class('actions', 'actions'); $table->setup(); foreach($feeds as $feed) { if (!empty($feed->preferredtitle)) { $feedtitle = s($feed->preferredtitle); } else { $feedtitle = $feed->title; } $viewlink = html_writer::link($CFG->wwwroot .'/blocks/rss_client/viewfeed.php?rssid=' . $feed->id . $extraparams, $feedtitle); $feedinfo = '
            ' . $viewlink . '
            ' . '
            ' . html_writer::link($feed->url, $feed->url) .'
            ' . '
            ' . $feed->description . '
            '; if ($feed->skipuntil) { $skipuntil = userdate($feed->skipuntil, get_string('strftimedatetime', 'langconfig')); $skipmsg = get_string('failedfeed', 'block_rss_client', $skipuntil); $notification = new \core\output\notification($skipmsg, 'error'); $notification->set_show_closebutton(false); $feedinfo .= $OUTPUT->render($notification); } $editurl = new moodle_url('/blocks/rss_client/editfeed.php?rssid=' . $feed->id . $extraparams); $editaction = $OUTPUT->action_icon($editurl, new pix_icon('t/edit', get_string('edit'))); $deleteurl = new moodle_url('/blocks/rss_client/managefeeds.php?deleterssid=' . $feed->id . '&sesskey=' . sesskey() . $extraparams); $deleteicon = new pix_icon('t/delete', get_string('delete')); $deleteaction = $OUTPUT->action_icon($deleteurl, $deleteicon, new confirm_action(get_string('deletefeedconfirm', 'block_rss_client'))); $feedicons = $editaction . ' ' . $deleteaction; $table->add_data(array($feedinfo, $feedicons)); } $table->print_html(); $url = $CFG->wwwroot . '/blocks/rss_client/editfeed.php?' . substr($extraparams, 1); echo '
            ' . $OUTPUT->single_button($url, get_string('addnewfeed', 'block_rss_client'), 'get') . '
            '; if ($returnurl) { echo ''; } echo $OUTPUT->footer(); PK'9\Z@@'rss_client/classes/privacy/provider.phpnu[. /** * Privacy class for requesting user data. * * @package block_rss_client * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_client\privacy; defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use \core_privacy\local\request\userlist; use \core_privacy\local\request\approved_userlist; /** * Privacy class for requesting user data. * * @package block_rss_client * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { $collection->add_database_table('block_rss_client', [ 'userid' => 'privacy:metadata:block_rss_client:userid', 'title' => 'privacy:metadata:block_rss_client:title', 'preferredtitle' => 'privacy:metadata:block_rss_client:preferredtitle', 'description' => 'privacy:metadata:block_rss_client:description', 'shared' => 'privacy:metadata:block_rss_client:shared', 'url' => 'privacy:metadata:block_rss_client:url', 'skiptime' => 'privacy:metadata:block_rss_client:skiptime', 'skipuntil' => 'privacy:metadata:block_rss_client:skipuntil', ], 'privacy:metadata:block_rss_client:tableexplanation'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { $sql = "SELECT ctx.id FROM {block_rss_client} brc JOIN {user} u ON brc.userid = u.id JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel WHERE brc.userid = :userid"; $params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER]; $contextlist = new contextlist(); $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users within a specific context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); if (!$context instanceof \context_user) { return; } $sql = "SELECT userid FROM {block_rss_client} WHERE userid = ?"; $params = [$context->instanceid]; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { $rssdata = []; $results = static::get_records($contextlist->get_user()->id); foreach ($results as $result) { $rssdata[] = (object) [ 'title' => $result->title, 'preferredtitle' => $result->preferredtitle, 'description' => $result->description, 'shared' => \core_privacy\local\request\transform::yesno($result->shared), 'url' => $result->url ]; } if (!empty($rssdata)) { $data = (object) [ 'feeds' => $rssdata, ]; \core_privacy\local\request\writer::with_context($contextlist->current())->export_data([ get_string('pluginname', 'block_rss_client')], $data); } } /** * Delete all use data which matches the specified deletion_criteria. * * @param \context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { if ($context instanceof \context_user) { static::delete_data($context->instanceid); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { $context = $userlist->get_context(); if ($context instanceof \context_user) { static::delete_data($context->instanceid); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { static::delete_data($contextlist->get_user()->id); } /** * Delete data related to a userid. * * @param int $userid The user ID */ protected static function delete_data($userid) { global $DB; $DB->delete_records('block_rss_client', ['userid' => $userid]); } /** * Get records related to this plugin and user. * * @param int $userid The user ID * @return array An array of records. */ protected static function get_records($userid) { global $DB; return $DB->get_records('block_rss_client', ['userid' => $userid]); } } PK'9\P##"rss_client/classes/output/feed.phpnu[. /** * Contains class block_rss_client\output\feed * * @package block_rss_client * @copyright 2015 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_client\output; defined('MOODLE_INTERNAL') || die(); /** * Class to help display an RSS Feed * * @package block_rss_client * @copyright 2015 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class feed implements \renderable, \templatable { /** * The feed's title * * @var string */ protected $title = null; /** * The feed's channel link. * * @var string|null */ protected ?string $channellink; /** * An array of renderable feed items * * @var array */ protected $items = array(); /** * The channel image * * @var channel_image */ protected $image = null; /** * Whether or not to show the title * * @var boolean */ protected $showtitle; /** * Whether or not to show the channel image * * @var boolean */ protected $showimage; /** * Contructor * * @param string $title The title of the RSS feed * @param boolean $showtitle Whether to show the title * @param boolean $showimage Whether to show the channel image * @param string|null $channellink The channel link of the RSS feed */ public function __construct($title, $showtitle = true, $showimage = true, ?string $channellink = null) { $this->title = $title; $this->showtitle = $showtitle; $this->showimage = $showimage; $this->channellink = $channellink; } /** * Export this for use in a mustache template context. * * @see templatable::export_for_template() * @param \renderer_base $output * @return array */ public function export_for_template(\renderer_base $output) { $data = array( 'title' => $this->showtitle ? $this->title : null, 'image' => null, 'items' => array(), 'channellink' => $this->channellink ?? null, ); if ($this->showimage && $this->image) { $data['image'] = $this->image->export_for_template($output); } foreach ($this->items as $item) { $data['items'][] = $item->export_for_template($output); } return $data; } /** * Set the feed title * * @param string $title * @return \block_rss_client\output\feed */ public function set_title($title) { $this->title = $title; return $this; } /** * Get the feed title * * @return string */ public function get_title() { return $this->title; } /** * Set the feed channel link. * * @param \moodle_url|null $channellink the URL to the channel website. */ public function set_channellink(?\moodle_url $channellink) { $this->channellink = $channellink; } /** * Add an RSS item * * @param \block_rss_client\output\item $item */ public function add_item(item $item) { $this->items[] = $item; return $this; } /** * Set the RSS items * * @param array $items An array of renderable RSS items */ public function set_items(array $items) { $this->items = $items; return $this; } /** * Get the RSS items * * @return array An array of renderable RSS items */ public function get_items() { return $this->items; } /** * Set the channel image * * @param \block_rss_client\output\channel_image $image */ public function set_image(channel_image $image) { $this->image = $image; } /** * Get the channel image * * @return channel_image */ public function get_image() { return $this->image; } /** * Set showtitle * * @param boolean $showtitle * @return \block_rss_client\output\feed */ public function set_showtitle($showtitle) { $this->showtitle = boolval($showtitle); return $this; } /** * Get showtitle * * @return boolean */ public function get_showtitle() { return $this->showtitle; } /** * Set showimage * * @param boolean $showimage * @return \block_rss_client\output\feed */ public function set_showimage($showimage) { $this->showimage = boolval($showimage); return $this; } /** * Get showimage * * @return boolean */ public function get_showimage() { return $this->showimage; } } PK'9\+ &rss_client/classes/output/renderer.phpnu[. /** * Contains class block_rss_client\output\block_renderer_html * * @package block_rss_client * @copyright 2015 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_client\output; defined('MOODLE_INTERNAL') || die(); /** * Renderer for RSS Client block * * @package block_rss_client * @copyright 2015 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends \plugin_renderer_base { /** * Render an RSS Item * * @param \templatable $item * @return string */ public function render_item(\templatable $item) { $data = $item->export_for_template($this); return $this->render_from_template('block_rss_client/item', $data); } /** * Render an RSS Feed * * @param \templatable $feed * @return string */ public function render_feed(\templatable $feed) { $data = $feed->export_for_template($this); return $this->render_from_template('block_rss_client/feed', $data); } /** * Render an RSS feeds block * * @param \templatable $block * @return string */ public function render_block(\templatable $block) { $data = $block->export_for_template($this); return $this->render_from_template('block_rss_client/block', $data); } /** * Render the block footer * * @param \templatable $footer * @return string */ public function render_footer(\templatable $footer) { $data = $footer->export_for_template($this); return $this->render_from_template('block_rss_client/footer', $data); } /** * Format a timestamp to use as a published date * * @param int $timestamp Unix timestamp * @return string */ public function format_published_date($timestamp) { if (empty($timestamp)) { return ''; } else { return \core_date::strftime(get_string('strftimerecentfull', 'langconfig'), $timestamp); } } /** * Format an RSS item title * * @param string $title * @return string */ public function format_title($title) { return break_up_long_words($title, 30); } /** * Format an RSS item description * * @param string $description * @return string */ public function format_description($description) { $description = format_text($description, FORMAT_HTML, array('para' => false)); $description = break_up_long_words($description, 30); return $description; } } PK'9\77+rss_client/classes/output/channel_image.phpnu[. /** * Contains class block_rss_client\output\channel_image * * @package block_rss_client * @copyright 2016 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_client\output; defined('MOODLE_INTERNAL') || die(); /** * Class to display RSS channel images * * @package block_rss_client * @copyright 2016 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class channel_image implements \renderable, \templatable { /** * The URL location of the image * * @var string */ protected $url; /** * The title of the image * * @var string */ protected $title; /** * The URL of the image link * * @var string */ protected $link; /** * Contructor * * @param \moodle_url $url The URL location of the image * @param string $title The title of the image * @param \moodle_url $link The URL of the image link */ public function __construct(\moodle_url $url, $title, ?\moodle_url $link = null) { $this->url = $url; $this->title = $title; $this->link = $link; } /** * Export this for use in a mustache template context. * * @see templatable::export_for_template() * @param renderer_base $output * @return array The data for the template */ public function export_for_template(\renderer_base $output) { return array( 'url' => clean_param($this->url, PARAM_URL), 'title' => $this->title, 'link' => clean_param($this->link, PARAM_URL), ); } /** * Set the URL * * @param \moodle_url $url * @return \block_rss_client\output\channel_image */ public function set_url(\moodle_url $url) { $this->url = $url; return $this; } /** * Get the URL * * @return \moodle_url */ public function get_url() { return $this->url; } /** * Set the title * * @param string $title * @return \block_rss_client\output\channel_image */ public function set_title($title) { $this->title = $title; return $this; } /** * Get the title * * @return string */ public function get_title() { return $this->title; } /** * Set the link * * @param \moodle_url $link * @return \block_rss_client\output\channel_image */ public function set_link($link) { $this->link = $link; return $this; } /** * Get the link * * @return \moodle_url */ public function get_link() { return $this->link; } } PK'9\KқK K #rss_client/classes/output/block.phpnu[. /** * Contains class block_rss_client\output\block * * @package block_rss_client * @copyright 2015 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_client\output; defined('MOODLE_INTERNAL') || die(); /** * Class to help display an RSS Feeds block * * @package block_rss_client * @copyright 2016 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block implements \renderable, \templatable { /** * An array of renderable feeds * * @var array */ protected $feeds; /** * Contruct * * @param array $feeds An array of renderable feeds */ public function __construct(array $feeds = array()) { $this->feeds = $feeds; } /** * Prepare data for use in a template * * @param \renderer_base $output * @return array */ public function export_for_template(\renderer_base $output) { $data = array('feeds' => array()); foreach ($this->feeds as $feed) { $data['feeds'][] = $feed->export_for_template($output); } return $data; } /** * Add a feed * * @param \block_rss_client\output\feed $feed * @return \block_rss_client\output\block */ public function add_feed(feed $feed) { $this->feeds[] = $feed; return $this; } /** * Set the feeds * * @param array $feeds * @return \block_rss_client\output\block */ public function set_feeds(array $feeds) { $this->feeds = $feeds; return $this; } /** * Get feeds * * @return array */ public function get_feeds() { return $this->feeds; } } PK'9\q2uk"rss_client/classes/output/item.phpnu[. /** * Contains class block_rss_client\output\feed * * @package block_rss_client * @copyright 2015 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_client\output; defined('MOODLE_INTERNAL') || die(); /** * Class to help display an RSS Item * * @package block_rss_client * @copyright 2015 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class item implements \renderable, \templatable { /** * The unique id of the item * * @var string */ protected $id; /** * The link to the item * * @var \moodle_url */ protected $link; /** * The title of the item * * @var string */ protected $title; /** * The description of the item * * @var string */ protected $description; /** * The item's permalink * * @var \moodle_url */ protected $permalink; /** * The publish date of the item in Unix timestamp format * * @var int */ protected $timestamp; /** * Whether or not to show the item's description * * @var string */ protected $showdescription; /** * Contructor * * @param string $id The id of the RSS item * @param \moodle_url $link The URL of the RSS item * @param string $title The title pf the RSS item * @param string $description The description of the RSS item * @param \moodle_url $permalink The permalink of the RSS item * @param int $timestamp The Unix timestamp that represents the published date * @param boolean $showdescription Whether or not to show the description */ public function __construct($id, \moodle_url $link, $title, $description, \moodle_url $permalink, $timestamp, $showdescription = true) { $this->id = $id; $this->link = $link; $this->title = $title; $this->description = $description; $this->permalink = $permalink; $this->timestamp = $timestamp; $this->showdescription = $showdescription; } /** * Export context for use in mustache templates * * @see templatable::export_for_template() * @param \renderer_base $output * @return array */ public function export_for_template(\renderer_base $output) { $data = array( 'id' => $this->id, 'permalink' => clean_param($this->permalink, PARAM_URL), 'datepublished' => $output->format_published_date($this->timestamp), 'link' => clean_param($this->link, PARAM_URL), ); // If the item does not have a title, create one from the description. $title = $this->title; if (!$title) { $title = strip_tags($this->description); $title = \core_text::substr($title, 0, 20) . '...'; } // Allow the renderer to format the title and description. $data['title'] = strip_tags($output->format_title($title)); $data['description'] = $this->showdescription ? $output->format_description($this->description) : null; return $data; } /** * Set id * * @param string $id * @return \block_rss_client\output\item */ public function set_id($id) { $this->id = $id; return $this; } /** * Get id * * @return string */ public function get_id() { return $this->id; } /** * Set link * * @param \moodle_url $link * @return \block_rss_client\output\item */ public function set_link(\moodle_url $link) { $this->link = $link; return $this; } /** * Get link * * @return \moodle_url */ public function get_link() { return $this->link; } /** * Set title * * @param string $title * @return \block_rss_client\output\item */ public function set_title($title) { $this->title = $title; return $this; } /** * Get title * * @return string */ public function get_title() { return $this->title; } /** * Set description * * @param string $description * @return \block_rss_client\output\item */ public function set_description($description) { $this->description = $description; return $this; } /** * Get description * * @return string */ public function get_description() { return $this->description; } /** * Set permalink * * @param string $permalink * @return \block_rss_client\output\item */ public function set_permalink($permalink) { $this->permalink = $permalink; return $this; } /** * Get permalink * * @return string */ public function get_permalink() { return $this->permalink; } /** * Set timestamp * * @param int $timestamp * @return \block_rss_client\output\item */ public function set_timestamp($timestamp) { $this->timestamp = $timestamp; return $this; } /** * Get timestamp * * @return string */ public function get_timestamp() { return $this->timestamp; } /** * Set showdescription * * @param boolean $showdescription * @return \block_rss_client\output\item */ public function set_showdescription($showdescription) { $this->showdescription = boolval($showdescription); return $this; } /** * Get showdescription * * @return boolean */ public function get_showdescription() { return $this->showdescription; } } PK'9\VD $rss_client/classes/output/footer.phpnu[. /** * Contains class block_rss_client\output\footer * * @package block_rss_client * @copyright 2015 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_client\output; defined('MOODLE_INTERNAL') || die(); /** * Class to help display an RSS Block footer * * @package block_rss_client * @copyright 2015 Howard County Public School System * @author Brendan Anderson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class footer implements \renderable, \templatable { /** * The link provided in the RSS channel * * @var \moodle_url|null */ protected $channelurl; /** * Link to manage feeds, only provided if a feed has failed. * * @var \moodle_url|null */ protected $manageurl = null; /** * Constructor * * @param \moodle_url $channelurl (optional) The link provided in the RSS channel */ public function __construct($channelurl = null) { $this->channelurl = $channelurl; } /** * Set the channel url * * @param \moodle_url $channelurl * @return \block_rss_client\output\footer */ public function set_channelurl(\moodle_url $channelurl) { $this->channelurl = $channelurl; return $this; } /** * Record the fact that there is at least one failed feed (and the URL for viewing * these failed feeds). * * @param \moodle_url $manageurl the URL to link to for more information */ public function set_failed(\moodle_url $manageurl) { $this->manageurl = $manageurl; } /** * Get the channel url * * @return \moodle_url */ public function get_channelurl() { return $this->channelurl; } /** * Export context for use in mustache templates * * @see templatable::export_for_template() * @param renderer_base $output * @return stdClass */ public function export_for_template(\renderer_base $output) { $data = new \stdClass(); if ($this->manageurl) { $data->hasfailedfeeds = true; $data->manageurl = clean_param($this->manageurl, PARAM_URL); } return $data; } } PK'9\P~(rss_client/classes/task/refreshfeeds.phpnu[. /** * Task for updating RSS feeds for rss client block * * @package block_rss_client * @author Farhan Karmali * @copyright Farhan Karmali 2018 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_client\task; defined('MOODLE_INTERNAL') || die(); /** * Task for updating RSS feeds for rss client block * * @package block_rss_client * @author Farhan Karmali * @copyright Farhan Karmali 2018 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class refreshfeeds extends \core\task\scheduled_task { /** The maximum time in seconds that cron will wait between attempts to retry failing RSS feeds. */ const CLIENT_MAX_SKIPTIME = HOURSECS * 12; /** * Name for this task. * * @return string */ public function get_name() { return get_string('refreshfeedstask', 'block_rss_client'); } /** * This task goes through all the feeds. If the feed has a skipuntil value * that is less than the current time cron will attempt to retrieve it * with the cache duration set to 0 in order to force the retrieval of * the item and refresh the cache. * * If a feed fails then the skipuntil time of that feed is set to be * later than the next expected task time. The amount of time will * increase each time the fetch fails until the maximum is reached. * * If a feed that has been failing is successfully retrieved it will * go back to being handled as though it had never failed. * * Task should therefore process requests for permanently broken RSS * feeds infrequently, and temporarily unavailable feeds will be tried * less often until they become available again. */ public function execute() { global $CFG, $DB; require_once("{$CFG->libdir}/simplepie/moodle_simplepie.php"); // We are going to measure execution times. $starttime = microtime(); $starttimesec = time(); // Fetch all site feeds. $rs = $DB->get_recordset('block_rss_client'); $counter = 0; mtrace(''); foreach ($rs as $rec) { mtrace(' ' . $rec->url . ' ', ''); // Skip feed if it failed recently. if ($starttimesec < $rec->skipuntil) { mtrace('skipping until ' . userdate($rec->skipuntil)); continue; } $feed = $this->fetch_feed($rec->url); if ($feed->error()) { // Skip this feed (for an ever-increasing time if it keeps failing). $rec->skiptime = $this->calculate_skiptime($rec->skiptime); $rec->skipuntil = time() + $rec->skiptime; $DB->update_record('block_rss_client', $rec); mtrace("Error: could not load/find the RSS feed - skipping for {$rec->skiptime} seconds."); } else { mtrace ('ok'); // It worked this time, so reset the skiptime. if ($rec->skiptime > 0) { $rec->skiptime = 0; $rec->skipuntil = 0; $DB->update_record('block_rss_client', $rec); } // Only increase the counter when a feed is sucesfully refreshed. $counter ++; } } $rs->close(); // Show times. mtrace($counter . ' feeds refreshed (took ' . microtime_diff($starttime, microtime()) . ' seconds)'); } /** * Fetch a feed for the specified URL. * * @param string $url The URL to fetch * @return \moodle_simplepie */ protected function fetch_feed(string $url): \moodle_simplepie { // Fetch the rss feed, using standard simplepie caching so feeds will be renewed only if cache has expired. \core_php_time_limit::raise(60); $feed = new \moodle_simplepie(); // Set timeout for longer than normal to be agressive at fetching feeds if possible.. $feed->set_timeout(40); $feed->set_cache_duration(0); $feed->set_feed_url($url); $feed->init(); return $feed; } /** * Calculates a new skip time for a record based on the current skip time. * * @param int $currentskip The current skip time of a record. * @return int The newly calculated skip time. */ protected function calculate_skiptime(int $currentskip): int { // If the feed has never failed, then the initial skiptime will be 0. We use a default of 5 minutes in this case. // If the feed has previously failed then we double that time. $newskiptime = max(MINSECS * 5, ($currentskip * 2)); // Max out at the CLIENT_MAX_SKIPTIME. return min($newskiptime, self::CLIENT_MAX_SKIPTIME); } } PK'9\)p "rss_client/templates/feed.mustachenu[{{! 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 . }} {{! @template block_rss_client/feed Template which defines an item in an RSS Feed Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * channel_image - object: URL, title and link for the channel image. * title - string: The title of the feed. * items - array: An array of feed items. Example context (json): { "title": "News from around my living room", "image": { "url": "https://www.example.com/feeds/news/poster.jpg", "title": "Example News Logo", "link": "https://www.example.com/feeds/news/" }, "feeditems": [ { "id": "https://www.example.com/node/12", "link": "https://www.example.com/my-turtle-story.html", "title": "My Turtle Story", "description": "This is a story about my turtle.", "permalink": "https://www.example.com/my-turtle-story.html", "datepublished": "11 January 2016, 7:11 pm" }, { "id": "https://www.example.com/node/12", "link": "https://www.example.com/my-cat-story.html", "title": "My Story", "description": "This is a story about my cats.", "permalink": "https://www.example.com/my-cat-story.html", "datepublished": "12 January 2016, 9:12 pm" } ], "channellink": "https://www.example.com" } }} {{$image}} {{#image}} {{> block_rss_client/channel_image}} {{/image}} {{/image}} {{$title}} {{#title}}
            {{title}}
            {{/title}} {{/title}} {{$items}}
              {{#items}} {{> block_rss_client/item}} {{/items}}
            {{/items}} {{#channellink}} {{/channellink}} PK'9\w6#rss_client/templates/block.mustachenu[{{! 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 . }} {{! @template block_rss_client/block Template which defines an RSS Feeds block Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * feeds - array: An array of RSS feeds. Example context (json): { "feeds": [ { "title": "News from around my living room", "image": { "url": "https://www.example.com/feeds/news/poster.jpg", "title": "Example News Logo", "link": "https://www.example.com/feeds/news/" }, "items": [ { "id": "https://www.example.com/node/12", "link": "https://www.example.com/my-turtle-story.html", "title": "My Turtle Story", "description": "This is a story about my turtle.", "permalink": "https://www.example.com/my-turtle-story.html", "datepublished": "11 January 2016, 7:11 pm" }, { "id": "https://www.example.com/node/12", "link": "https://www.example.com/my-cat-story.html", "title": "My Story", "description": "This is a story about my cats.", "permalink": "https://www.example.com/my-cat-story.html", "datepublished": "12 January 2016, 9:12 pm" } ], "channellink": "https://www.example.com" }, { "title": "News from around my kitchen", "image": { "url": "https://www.example.com/feeds/news/kitchen.jpg", "title": "Picture of My Kitchen", "link": "https://www.example.com/feeds/news/kitchen/" }, "items": [ { "id": "https://www.example.com/node/10", "link": "https://www.example.com/oven-smoke.html", "title": "Why is the Oven Smoking?", "description": "There is something smoking in the oven.", "permalink": "https://www.example.com/oven-smoke.html", "datepublished": "10 January 2016, 1:13 pm" }, { "id": "https://www.example.com/node/13", "link": "https://www.example.com/coffee-is-good.html", "title": "Why My Coffee Machine is So Great!", "description": "Don't be fancy; drips are best.", "permalink": "https://www.example.com/oven-smoke.html", "datepublished": "13 January 2016, 8:25 pm" } ], "channellink": "https://www.example.com" } ] } }} {{#feeds}} {{> block_rss_client/feed}} {{/feeds}} PK'9\b<. }} {{! @template block_rss_client/item Template which defines an item in an RSS Feed Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * id - string: A unique id for the feed item. * link - string: The URL of the feed item. Must already be escaped. * title - string: The title of the feed item. * description - string: The text description of the feed item. * permalink - string: The permalink of the feed item. Must already be escaped. * datepublished - string: The date the feed item was published. Example context (json): { "id": "https://www.example.com/node", "link": "https://www.example.com/my-cat-story.html", "title": "My Story", "description": "This is a story about my cats.", "permalink": "https://www.example.com/my-cat-story.html", "datepublished": "12 January 2016, 9:12 pm" } }}
          • {{$title}} {{/title}} {{$content}} {{#description}}
            {{{datepublished}}}
            {{{description}}}
            {{/description}} {{/content}}
          • PK'9\A+rss_client/templates/channel_image.mustachenu[{{! 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 . }} {{! @template block_rss_client/channel_image Template which defines an item in an RSS Feed Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * url - string: The escaped URL of the image. * title - string: The title of the image. * link - string: Optionally, a URL to link the image to. Must be escaped. Example context (json): { "url": "http://www.example.com/images/catpic.jpg", "title": "A picture of my cat", "link": "http://www.example.com/cat-news/" } }}
            {{#link}} {{/link}} {{title}} {{#link}} {{/link}}
            PK'9\h$rss_client/templates/footer.mustachenu[{{! 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 . }} {{! @template block_rss_client/footer Template which defines an item in an RSS Feed Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * channellink - string: The channel URL. Must be escaped. Example context (json): { "hasfailedfeeds": true, "manageurl": "http://moodle.web/blocks/rss_client/managefeeds.php?courseid=1" } }} {{#hasfailedfeeds}} {{#str}} failedfeeds, block_rss_client {{/str}} {{/hasfailedfeeds}} PK'9\:,,rss_client/block_rss_client.phpnu[. /** * Contains block_rss_client * @package block_rss_client * @copyright Daryl Hawes * @license http://www.gnu.org/copyleft/gpl.html GNU GPL */ /** * A block which displays Remote feeds * * @package block_rss_client * @copyright Daryl Hawes * @license http://www.gnu.org/copyleft/gpl.html GNU GPL */ class block_rss_client extends block_base { /** @var bool track whether any of the output feeds have recorded failures */ private $hasfailedfeeds = false; function init() { $this->title = get_string('pluginname', 'block_rss_client'); } function applicable_formats() { return array('all' => true, 'tag' => false); // Needs work to make it work on tags MDL-11960 } function specialization() { // After the block has been loaded we customize the block's title display if (!empty($this->config) && !empty($this->config->title)) { // There is a customized block title, display it $this->title = $this->config->title; } else { // No customized block title, use localized remote news feed string $this->title = get_string('remotenewsfeed', 'block_rss_client'); } } /** * Gets the footer, which is the channel link of the last feed in our list of feeds * * @param array $feedrecords The feed records from the database. * @return block_rss_client\output\footer|null The renderable footer or null if none should be displayed. */ protected function get_footer($feedrecords) { $footer = null; if ($this->hasfailedfeeds) { if (has_any_capability(['block/rss_client:manageownfeeds', 'block/rss_client:manageanyfeeds'], $this->context)) { if ($footer === null) { $footer = new block_rss_client\output\footer(); } $manageurl = new moodle_url('/blocks/rss_client/managefeeds.php', ['courseid' => $this->page->course->id]); $footer->set_failed($manageurl); } } return $footer; } function get_content() { global $CFG, $DB; if ($this->content !== NULL) { return $this->content; } // initalise block content object $this->content = new stdClass; $this->content->text = ''; $this->content->footer = ''; if (empty($this->instance)) { return $this->content; } $managefeedfooterlink = ''; if (has_any_capability(['block/rss_client:manageanyfeeds', 'block/rss_client:manageownfeeds'], $this->context)) { $managefeedfooterlink = html_writer::link( new moodle_url('/blocks/rss_client/managefeeds.php', ['courseid' => $this->page->course->id]), get_string('managefeeds', 'block_rss_client'), ['class' => 'btn btn-primary', 'role' => 'button'], ); } if (!isset($this->config)) { // The block has yet to be configured - just display configure message in // the block if user has permission to configure it if (has_capability('block/rss_client:manageanyfeeds', $this->context)) { $this->content->text = get_string('feedsconfigurenewinstance2', 'block_rss_client'); } $this->content->footer = $managefeedfooterlink; return $this->content; } // How many feed items should we display? $maxentries = 5; if ( !empty($this->config->shownumentries) ) { $maxentries = intval($this->config->shownumentries); }elseif( isset($CFG->block_rss_client_num_entries) ) { $maxentries = intval($CFG->block_rss_client_num_entries); } /* --------------------------------- * Begin Normal Display of Block Content * --------------------------------- */ $renderer = $this->page->get_renderer('block_rss_client'); $block = new \block_rss_client\output\block(); if (!empty($this->config->rssid)) { list($rssidssql, $params) = $DB->get_in_or_equal($this->config->rssid); $rssfeeds = $DB->get_records_select('block_rss_client', "id $rssidssql", $params); if (!empty($rssfeeds)) { $showtitle = false; if (count($rssfeeds) > 1) { // When many feeds show the title for each feed. $showtitle = true; } foreach ($rssfeeds as $feed) { if ($renderablefeed = $this->get_feed($feed, $maxentries, $showtitle)) { $block->add_feed($renderablefeed); } } $footer = $this->get_footer($rssfeeds); } } $this->content->text = $renderer->render_block($block); if (isset($footer)) { $this->content->footer = $renderer->render_footer($footer); } $this->content->footer .= $managefeedfooterlink; return $this->content; } function instance_allow_multiple() { return true; } function has_config() { return true; } function instance_allow_config() { return true; } /** * Returns the html of a feed to be displaed in the block * * @param mixed feedrecord The feed record from the database * @param int maxentries The maximum number of entries to be displayed * @param boolean showtitle Should the feed title be displayed in html * @return block_rss_client\output\feed|null The renderable feed or null of there is an error */ public function get_feed($feedrecord, $maxentries, $showtitle) { global $CFG; require_once($CFG->libdir.'/simplepie/moodle_simplepie.php'); if ($feedrecord->skipuntil) { // Last attempt to gather this feed via cron failed - do not try to fetch it now. $this->hasfailedfeeds = true; return null; } $simplepiefeed = new moodle_simplepie($feedrecord->url); if(isset($CFG->block_rss_client_timeout)){ $simplepiefeed->set_cache_duration($CFG->block_rss_client_timeout * 60); } if ($simplepiefeed->error()) { debugging($feedrecord->url .' Failed with code: '.$simplepiefeed->error()); return null; } if(empty($feedrecord->preferredtitle)){ // Simplepie does escape HTML entities. $feedtitle = $this->format_title($simplepiefeed->get_title()); }else{ // Moodle custom title does not does escape HTML entities. $feedtitle = $this->format_title(s($feedrecord->preferredtitle)); } if (empty($this->config->title)){ //NOTE: this means the 'last feed' displayed wins the block title - but //this is exiting behaviour.. $this->title = strip_tags($feedtitle); } $feed = new \block_rss_client\output\feed($feedtitle, $showtitle, $this->config->block_rss_client_show_channel_image); if ($simplepieitems = $simplepiefeed->get_items(0, $maxentries)) { foreach ($simplepieitems as $simplepieitem) { try { $item = new \block_rss_client\output\item( $simplepieitem->get_id(), new moodle_url($simplepieitem->get_link()), $simplepieitem->get_title(), $simplepieitem->get_description(), new moodle_url($simplepieitem->get_permalink()), $simplepieitem->get_date('U'), $this->config->display_description ); $feed->add_item($item); } catch (moodle_exception $e) { // If there is an error with the RSS item, we don't // want to crash the page. Specifically, moodle_url can // throw an exception of the param is an extremely // malformed url. debugging($e->getMessage()); } } } // Feed image. if ($imageurl = $simplepiefeed->get_image_url()) { try { $image = new \block_rss_client\output\channel_image( new moodle_url($imageurl), $simplepiefeed->get_image_title(), new moodle_url($simplepiefeed->get_image_link()) ); $feed->set_image($image); } catch (moodle_exception $e) { // If there is an error with the RSS image, we don'twant to // crash the page. Specifically, moodle_url can throw an // exception if the param is an extremely malformed url. debugging($e->getMessage()); } } // Feed channel link. if ($this->config->block_rss_client_show_channel_link) { $channellink = $simplepiefeed->get_link(); $feed->set_channellink($channellink ? new moodle_url($channellink) : null); } return $feed; } /** * Strips a large title to size and adds ... if title too long * This function does not escape HTML entities, so they have to be escaped * before being passed here. * * @param string title to shorten * @param int max character length of title * @return string title shortened if necessary */ function format_title($title,$max=64) { if (core_text::strlen($title) <= $max) { return $title; } else { return core_text::substr($title, 0, $max - 3) . '...'; } } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { global $CFG; // Return all settings for all users since it is safe (no private keys, etc..). $instanceconfigs = !empty($this->config) ? $this->config : new stdClass(); $pluginconfigs = (object) [ 'num_entries' => $CFG->block_rss_client_num_entries, 'timeout' => $CFG->block_rss_client_timeout ]; return (object) [ 'instance' => $instanceconfigs, 'plugin' => $pluginconfigs, ]; } } PK'9\Hǯ""*rss_client/tests/privacy/provider_test.phpnu[. /** * Base class for unit tests for block_rss_client. * * @package block_rss_client * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_client\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\tests\provider_testcase; use block_rss_client\privacy\provider; use core_privacy\local\request\approved_userlist; /** * Unit tests for blocks\rss_client\classes\privacy\provider.php * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { /** * Basic setup for these tests. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(true); } /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid(): void { $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); $this->add_rss_feed($user); $contextlist = provider::get_contexts_for_userid($user->id); $this->assertEquals($context, $contextlist->current()); } /** * Test that data is exported correctly for this plugin. */ public function test_export_user_data(): void { $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); $this->add_rss_feed($user); $this->add_rss_feed($user); $writer = \core_privacy\local\request\writer::with_context($context); $this->assertFalse($writer->has_any_data()); $this->export_context_data_for_user($user->id, $context, 'block_rss_client'); $data = $writer->get_data([get_string('pluginname', 'block_rss_client')]); $this->assertCount(2, $data->feeds); $feed1 = reset($data->feeds); $this->assertEquals('BBC News - World', $feed1->title); $this->assertEquals('World News', $feed1->preferredtitle); $this->assertEquals('Description: BBC News - World', $feed1->description); $this->assertEquals(get_string('no'), $feed1->shared); $this->assertEquals('http://feeds.bbci.co.uk/news/world/rss.xml?edition=uk', $feed1->url); } /** * Test that only users within a course context are fetched. */ public function test_get_users_in_context(): void { $component = 'block_rss_client'; // Create a user. $user = $this->getDataGenerator()->create_user(); $usercontext = \context_user::instance($user->id); $userlist = new \core_privacy\local\request\userlist($usercontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); $this->add_rss_feed($user); // The list of users within the user context should contain user. provider::get_users_in_context($userlist); $this->assertCount(1, $userlist); $expected = [$user->id]; $actual = $userlist->get_userids(); $this->assertEquals($expected, $actual); // The list of users within the system context should be empty. $systemcontext = \context_system::instance(); $userlist2 = new \core_privacy\local\request\userlist($systemcontext, $component); provider::get_users_in_context($userlist2); $this->assertCount(0, $userlist2); } /** * Test that data for users in approved userlist is deleted. */ public function test_delete_data_for_users(): void { $component = 'block_rss_client'; $user1 = $this->getDataGenerator()->create_user(); $usercontext1 = \context_user::instance($user1->id); $user2 = $this->getDataGenerator()->create_user(); $usercontext2 = \context_user::instance($user2->id); $this->add_rss_feed($user1); $this->add_rss_feed($user2); $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); $expected = [$user1->id]; $actual = $userlist1->get_userids(); $this->assertEquals($expected, $actual); $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); $expected = [$user2->id]; $actual = $userlist2->get_userids(); $this->assertEquals($expected, $actual); // Convert $userlist1 into an approved_contextlist. $approvedlist1 = new approved_userlist($usercontext1, $component, $userlist1->get_userids()); // Delete using delete_data_for_user. provider::delete_data_for_users($approvedlist1); // Re-fetch users in usercontext1. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); // The user data in usercontext1 should be deleted. $this->assertCount(0, $userlist1); // Re-fetch users in usercontext2. $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); // The user data in usercontext2 should be still present. $this->assertCount(1, $userlist2); // Convert $userlist2 into an approved_contextlist in the system context. $systemcontext = \context_system::instance(); $approvedlist2 = new approved_userlist($systemcontext, $component, $userlist2->get_userids()); // Delete using delete_data_for_user. provider::delete_data_for_users($approvedlist2); // Re-fetch users in usercontext2. $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); // The user data in systemcontext should not be deleted. $this->assertCount(1, $userlist2); } /** * Test that user data is deleted using the context. */ public function test_delete_data_for_all_users_in_context(): void { global $DB; $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); $this->add_rss_feed($user); // Check that we have an entry. $rssfeeds = $DB->get_records('block_rss_client', ['userid' => $user->id]); $this->assertCount(1, $rssfeeds); provider::delete_data_for_all_users_in_context($context); // Check that it has now been deleted. $rssfeeds = $DB->get_records('block_rss_client', ['userid' => $user->id]); $this->assertCount(0, $rssfeeds); } /** * Test that user data is deleted for this user. */ public function test_delete_data_for_user(): void { global $DB; $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); $this->add_rss_feed($user); // Check that we have an entry. $rssfeeds = $DB->get_records('block_rss_client', ['userid' => $user->id]); $this->assertCount(1, $rssfeeds); $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'block_rss_feed', [$context->id]); provider::delete_data_for_user($approvedlist); // Check that it has now been deleted. $rssfeeds = $DB->get_records('block_rss_client', ['userid' => $user->id]); $this->assertCount(0, $rssfeeds); } /** * Add dummy rss feed. * * @param object $user User object */ private function add_rss_feed($user) { global $DB; $rssfeeddata = array( 'userid' => $user->id, 'title' => 'BBC News - World', 'preferredtitle' => 'World News', 'description' => 'Description: BBC News - World', 'shared' => 0, 'url' => 'http://feeds.bbci.co.uk/news/world/rss.xml?edition=uk', ); $DB->insert_record('block_rss_client', $rssfeeddata); } } PK'9\r&,rss_client/tests/cron_test.phpnu[. namespace block_rss_client; defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_rss_client.php'); /** * PHPunit tests for rss client cron. * * @package block_rss_client * @copyright 2015 Universit of Nottingham * @author Neill Magill * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class cron_test extends \advanced_testcase { /** * Test that when a record has a skipuntil time that is greater * than the current time the attempt is skipped. */ public function test_skip(): void { global $DB, $CFG; $this->resetAfterTest(); // Create a RSS feed record with a skip until time set to the future. $record = (object) array( 'userid' => 1, 'title' => 'Skip test feed', 'preferredtitle' => '', 'description' => 'A feed to test the skip time.', 'shared' => 0, 'url' => 'http://example.com/rss', 'skiptime' => 330, 'skipuntil' => time() + 300, ); $DB->insert_record('block_rss_client', $record); $task = new \block_rss_client\task\refreshfeeds(); ob_start(); // Silence SimplePie php notices. $errorlevel = error_reporting($CFG->debug & ~E_USER_NOTICE); $task->execute(); error_reporting($errorlevel); $cronoutput = ob_get_clean(); $this->assertStringContainsString('skipping until ' . userdate($record->skipuntil), $cronoutput); $this->assertStringContainsString('0 feeds refreshed (took ', $cronoutput); } /** * Data provider for skip time tests. * * @return array */ public static function skip_time_increase_provider(): array { return [ 'Never failed' => [ 'skiptime' => 0, 'skipuntil' => 0, 'newvalue' => MINSECS * 5, ], 'Failed before' => [ // This should just double the time. 'skiptime' => 330, 'skipuntil' => time(), 'newvalue' => 660, ], 'Near max' => [ 'skiptime' => \block_rss_client\task\refreshfeeds::CLIENT_MAX_SKIPTIME - 5, 'skipuntil' => time(), 'newvalue' => \block_rss_client\task\refreshfeeds::CLIENT_MAX_SKIPTIME, ], ]; } /** * Test that when a feed has an error the skip time is increased correctly. * * @dataProvider skip_time_increase_provider */ public function test_error($skiptime, $skipuntil, $newvalue): void { global $DB, $CFG; $this->resetAfterTest(); require_once("{$CFG->libdir}/simplepie/moodle_simplepie.php"); $time = time(); // A record that has failed before. $record = (object) [ 'userid' => 1, 'title' => 'Skip test feed', 'preferredtitle' => '', 'description' => 'A feed to test the skip time.', 'shared' => 0, 'url' => 'http://example.com/rss', 'skiptime' => $skiptime, 'skipuntil' => $skipuntil, ]; $record->id = $DB->insert_record('block_rss_client', $record); // Run the scheduled task and have it fail. $task = $this->getMockBuilder(\block_rss_client\task\refreshfeeds::class) ->onlyMethods(['fetch_feed']) ->getMock(); $piemock = $this->getMockBuilder(\moodle_simplepie::class) ->onlyMethods(['error']) ->getMock(); $piemock->method('error') ->willReturn(true); $task->method('fetch_feed') ->willReturn($piemock); // Run the cron and capture its output. $this->expectOutputRegex("/.*Error: could not load\/find the RSS feed - skipping for {$newvalue} seconds.*/"); $task->execute(); } } PK'9\;x 9rss_client/tests/behat/block_rss_client_frontpage.featurenu[@block @block_rss_client Feature: Enable RSS client block menu on the frontpage In order to enable the RSS client block on the frontpage As an admin I can add RSS client block to the frontpage Background: Given I log in as "admin" When I navigate to "Plugins > Blocks > Manage blocks" in site administration Then I enable "rss_client" "block" plugin And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | rss_client | System | 1 | site-index | side-pre | @javascript Scenario: Configuring the RSS block on the frontpage Given I log in as "admin" And I am on site homepage And I turn editing mode on And "RSS feed" "block" should exist And I configure the "RSS feed" block And I should see "There are not yet any RSS feeds. Choose 'Add new RSS feed'." # Test filling in an empty URL in the input. And I click on "Add new RSS feed" "radio" And I press "Save changes" And I should see "You must supply a value here." # Test filling in with a non-valid URL in the input. And I set the field "config_feedurl" to "https://example.com/notvalid.rss" And I press "Save changes" And I should see "Could not find or load the RSS feed." # Test filling in with the correct URL in the input. And I set the field "config_feedurl" to "https://www.nasa.gov/rss/dyn/breaking_news.rss" And I set the field "config_block_rss_client_show_channel_link" to "Yes" And I press "Save changes" And I should see "NASA" And I should see "Source site..." # Test the existence of the available feeds. When I configure the "NASA" block Then I should see "NASA" in the "Select feeds to display" "select" And I click on "Cancel" "button" in the "Configure NASA block" "dialogue" # Test the Manage RSS feeds page. And I click on "Manage RSS feeds" "link" And I should see "NASA" PK'9\". /** * Script to let a user edit the properties of a particular RSS feed. * * @package block_rss_client * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../../config.php'); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->libdir .'/simplepie/moodle_simplepie.php'); class feed_edit_form extends moodleform { protected $isadding; protected $caneditshared; protected $title = ''; protected $description = ''; function __construct($actionurl, $isadding, $caneditshared) { $this->isadding = $isadding; $this->caneditshared = $caneditshared; parent::__construct($actionurl); } function definition() { $mform =& $this->_form; // Then show the fields about where this block appears. $mform->addElement('header', 'rsseditfeedheader', get_string('feed', 'block_rss_client')); $mform->addElement('text', 'url', get_string('feedurl', 'block_rss_client'), array('size' => 60)); $mform->setType('url', PARAM_URL); $mform->addRule('url', null, 'required'); $mform->addElement('checkbox', 'autodiscovery', get_string('enableautodiscovery', 'block_rss_client')); $mform->setDefault('autodiscovery', 1); $mform->setAdvanced('autodiscovery'); $mform->addHelpButton('autodiscovery', 'enableautodiscovery', 'block_rss_client'); $mform->addElement('text', 'preferredtitle', get_string('customtitlelabel', 'block_rss_client'), array('size' => 60)); $mform->setType('preferredtitle', PARAM_NOTAGS); if ($this->caneditshared) { $mform->addElement('selectyesno', 'shared', get_string('sharedfeed', 'block_rss_client')); $mform->setDefault('shared', 0); } $submitlabal = null; // Default if ($this->isadding) { $submitlabal = get_string('addnewfeed', 'block_rss_client'); } $this->add_action_buttons(true, $submitlabal); } function definition_after_data(){ $mform =& $this->_form; if($mform->getElementValue('autodiscovery')){ $mform->applyFilter('url', 'feed_edit_form::autodiscover_feed_url'); } } function validation($data, $files) { $errors = parent::validation($data, $files); $rss = new moodle_simplepie(); // set timeout for longer than normal to try and grab the feed $rss->set_timeout(10); $rss->set_feed_url($data['url']); $rss->set_autodiscovery_cache_duration(0); $rss->set_autodiscovery_level(\SimplePie\SimplePie::LOCATOR_NONE); $rss->init(); if ($rss->error()) { $errors['url'] = get_string('couldnotfindloadrssfeed', 'block_rss_client'); } else { $this->title = $rss->get_title(); $this->description = $rss->get_description(); } return $errors; } function get_data() { $data = parent::get_data(); if ($data) { $data->title = ''; $data->description = ''; if($this->title){ $data->title = $this->title; } if($this->description){ $data->description = $this->description; } } return $data; } /** * Autodiscovers a feed url from a given url, to be used by the formslibs * filter function * * Uses simplepie with autodiscovery set to maximum level to try and find * a feed to subscribe to. * See: http://simplepie.org/wiki/reference/simplepie/set_autodiscovery_level * * @param string URL to autodiscover a url * @return string URL of feed or original url if none found */ public static function autodiscover_feed_url($url){ $rss = new moodle_simplepie(); $rss->set_feed_url($url); $rss->set_autodiscovery_level(\SimplePie\SimplePie::LOCATOR_ALL); // When autodiscovering an RSS feed, simplepie will try lots of // rss links on a page, so set the timeout high $rss->set_timeout(20); $rss->init(); if($rss->error()){ return $url; } // return URL without quoting.. $discoveredurl = new moodle_url($rss->subscribe_url()); return $discoveredurl->out(false); } } $returnurl = optional_param('returnurl', '', PARAM_LOCALURL); $courseid = optional_param('courseid', 0, PARAM_INT); $rssid = optional_param('rssid', 0, PARAM_INT); // 0 mean create new. if ($courseid == SITEID) { $courseid = 0; } if ($courseid) { $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); $PAGE->set_course($course); $context = $PAGE->context; } else { $context = context_system::instance(); $PAGE->set_context($context); } $managesharedfeeds = has_capability('block/rss_client:manageanyfeeds', $context); if (!$managesharedfeeds) { require_capability('block/rss_client:manageownfeeds', $context); } $urlparams = array('rssid' => $rssid); if ($courseid) { $urlparams['courseid'] = $courseid; } if ($returnurl) { $urlparams['returnurl'] = $returnurl; } $managefeeds = new moodle_url('/blocks/rss_client/managefeeds.php', $urlparams); $PAGE->set_url('/blocks/rss_client/editfeed.php', $urlparams); $PAGE->set_pagelayout('admin'); if ($rssid) { $isadding = false; if ($managesharedfeeds) { $select = 'id = :id AND (userid = :userid OR shared = 1)'; } else { $select = 'id = :id AND userid = :userid'; } $rssrecord = $DB->get_record_select('block_rss_client', $select, [ 'id' => $rssid, 'userid' => $USER->id, ], '*', MUST_EXIST); } else { $isadding = true; $rssrecord = new stdClass; } $mform = new feed_edit_form($PAGE->url, $isadding, $managesharedfeeds); $mform->set_data($rssrecord); if ($mform->is_cancelled()) { redirect($managefeeds); } else if ($data = $mform->get_data()) { $data->userid = $USER->id; if (!$managesharedfeeds) { $data->shared = 0; } if ($isadding) { $DB->insert_record('block_rss_client', $data); } else { $data->id = $rssid; $DB->update_record('block_rss_client', $data); } redirect($managefeeds); } else { if ($isadding) { $strtitle = get_string('addnewfeed', 'block_rss_client'); } else { $strtitle = get_string('editafeed', 'block_rss_client'); } $PAGE->set_title($strtitle); $PAGE->set_heading($strtitle); $PAGE->navbar->add(get_string('blocks')); $PAGE->navbar->add(get_string('pluginname', 'block_rss_client')); $PAGE->navbar->add(get_string('managefeeds', 'block_rss_client'), $managefeeds ); $PAGE->navbar->add($strtitle); echo $OUTPUT->header(); echo $OUTPUT->heading($strtitle, 2); $mform->display(); echo $OUTPUT->footer(); } PK'9\ʟmFFrss_client/settings.phpnu[. /** * Settings for the RSS client block. * * @package block_rss_client * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { $settings->add(new admin_setting_configtext('block_rss_client_num_entries', get_string('numentries', 'block_rss_client'), get_string('clientnumentries', 'block_rss_client'), 5, PARAM_INT)); $settings->add(new admin_setting_configtext('block_rss_client_timeout', get_string('timeout2', 'block_rss_client'), get_string('timeout', 'block_rss_client'), 30, PARAM_INT)); $link =''.get_string('feedsaddedit', 'block_rss_client').''; $settings->add(new admin_setting_heading('block_rss_addheading', '', $link)); }PK'9\B|ïonline_users/styles.cssnu[.block_online_users .content .list li.listentry { clear: both; } .block_online_users .content .list li.listentry:not(:first-child) { padding-top: 3px; } .block_online_users .content .list li.listentry .user { float: left; position: relative; } .block_online_users .content .list li.listentry .otherusers { margin-left: 1.5rem; } .block_online_users .content .list li.listentry .user .userpicture { vertical-align: text-bottom; } .block_online_users .content .list li.listentry .message, .block_online_users .content .list li.listentry .uservisibility { float: right; margin-top: 3px; } .block_online_users .content .info { text-align: center; } PK'9\G:&Gonline_users/db/access.phpnu[. /** * Online users block caps. * * @package block_online_users * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/online_users:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/online_users:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), 'block/online_users:viewlist' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'user' => CAP_ALLOW, 'guest' => CAP_PREVENT, 'student' => CAP_ALLOW, 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ) ) ); PK'9\ W~,,+online_users/lang/en/block_online_users.phpnu[. /** * Strings for component 'block_online_users', language 'en', branch 'MOODLE_20_STABLE' * * @package block_online_users * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['otherusers'] = 'Other users ({$a})'; $string['onlinestatushiding_desc'] = 'If enabled, users have the option to hide their online status from other users.'; $string['configtimetosee'] = 'Number of minutes determining the period of inactivity after which a user is no longer considered to be online.'; $string['onlinestatushiding'] = 'Online status hiding'; $string['nouser'] = 'No online users'; $string['numuser'] = '{$a} online user'; $string['numusers'] = '{$a} online users'; $string['online_users:addinstance'] = 'Add a new online users block'; $string['online_users:myaddinstance'] = 'Add a new online users block to Dashboard'; $string['online_users:viewlist'] = 'View list of online users'; $string['online_status:hide'] = 'Hide my online status from other users'; $string['online_status:show'] = 'Show my online status to other users'; $string['periodnminutes'] = 'last {$a} minutes'; $string['pluginname'] = 'Online users'; $string['timetosee'] = 'Remove after inactivity (minutes)'; $string['privacy:metadata:preference:uservisibility'] = 'Online status visible to other users in the Online users block.'; PK'9\i online_users/lib.phpnu[. /** * Contains functions called by core. * * @package block_online_users * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Callback to define user preferences. * * @return array */ function block_online_users_user_preferences() { $preferences = array(); $preferences['block_online_users_uservisibility'] = array( 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 1, 'choices' => array(0, 1), 'permissioncallback' => function($user, $preferencename) { global $USER; return $user->id == $USER->id; } ); return $preferences; } PK'9\1 _online_users/version.phpnu[. /** * Version details * * @package block_online_users * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_online_users'; // Full name of the plugin (used for diagnostics) PK'9\i )online_users/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_online_users. * * @package block_online_users * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_online_users\privacy; use core_privacy\local\metadata\collection; use core_privacy\local\request\writer; use core_privacy\local\request\transform; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_online_users. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\user_preference_provider { /** * Describe all the places where this plugin stores personal data. * * @param collection $collection Collection of items to add metadata to. * @return collection Collection with our added items. */ public static function get_metadata(collection $collection): collection { $collection->add_user_preference('block_online_users_uservisibility', 'privacy:metadata:preference:uservisibility'); return $collection; } /** * Export user preferences controlled by this plugin. * * @param int $userid ID of the user we are exporting data form. */ public static function export_user_preferences(int $userid) { $uservisibility = get_user_preferences('block_online_users_uservisibility', 1, $userid); writer::export_user_preference('block_online_users', 'block_online_users_uservisibility', transform::yesno($uservisibility), get_string('privacy:metadata:preference:uservisibility', 'block_online_users')); } } PK'9\<0 online_users/classes/fetcher.phpnu[. /** * File containing onlineusers class. * * @package block_online_users * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_online_users; defined('MOODLE_INTERNAL') || die(); /** * Class used to list and count online users * * @package block_online_users * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class fetcher { /** @var string The SQL query for retrieving a list of online users */ public $sql; /** @var string The SQL query for counting the number of online users */ public $csql; /** @var string The params for the SQL queries */ public $params; /** * Class constructor * * @param int $currentgroup The group (if any) to filter on * @param int $now Time now * @param int $timetoshowusers Number of seconds to show online users * @param context $context Context object used to generate the sql for users enrolled in a specific course * @param bool $sitelevel Whether to check online users at site level. * @param int $courseid The course id to check */ public function __construct($currentgroup, $now, $timetoshowusers, $context, $sitelevel = true, $courseid = null) { $this->set_sql($currentgroup, $now, $timetoshowusers, $context, $sitelevel, $courseid); } /** * Store the SQL queries & params for listing online users * * @param int $currentgroup The group (if any) to filter on * @param int $now Time now * @param int $timetoshowusers Number of seconds to show online users * @param context $context Context object used to generate the sql for users enrolled in a specific course * @param bool $sitelevel Whether to check online users at site level. * @param int $courseid The course id to check */ protected function set_sql($currentgroup, $now, $timetoshowusers, $context, $sitelevel, $courseid) { global $USER, $DB, $CFG; $timefrom = 100 * floor(($now - $timetoshowusers) / 100); // Round to nearest 100 seconds for better query cache. $groupmembers = ""; $groupselect = ""; $groupby = ""; $lastaccess = ", lastaccess"; $timeaccess = ", ul.timeaccess AS lastaccess"; $uservisibility = ""; $uservisibilityselect = ""; if ($CFG->block_online_users_onlinestatushiding) { $uservisibility = ", up.value AS uservisibility"; $uservisibilityselect = "AND (" . $DB->sql_cast_char2int('up.value', true) . " = 1 OR up.value IS NULL OR u.id = :userid)"; } $params = array(); $userfieldsapi = \core_user\fields::for_userpic()->including('username', 'deleted'); $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; // Add this to the SQL to show only group users. if ($currentgroup !== null) { $groupmembers = ", {groups_members} gm"; $groupselect = "AND u.id = gm.userid AND gm.groupid = :currentgroup"; $groupby = "GROUP BY $userfields"; $lastaccess = ", MAX(u.lastaccess) AS lastaccess"; $timeaccess = ", MAX(ul.timeaccess) AS lastaccess"; if ($CFG->block_online_users_onlinestatushiding) { $uservisibility = ", MAX(" . $DB->sql_cast_char2int('up.value', true) . ") AS uservisibility"; } $params['currentgroup'] = $currentgroup; } $params['now'] = $now; $params['timefrom'] = $timefrom; $params['userid'] = $USER->id; $params['name'] = 'block_online_users_uservisibility'; if ($sitelevel) { $sql = "SELECT $userfields $lastaccess $uservisibility FROM {user} u $groupmembers LEFT JOIN {user_preferences} up ON up.userid = u.id AND up.name = :name WHERE u.lastaccess > :timefrom AND u.lastaccess <= :now AND u.deleted = 0 $uservisibilityselect $groupselect $groupby ORDER BY lastaccess DESC "; $csql = "SELECT COUNT(u.id) FROM {user} u $groupmembers LEFT JOIN {user_preferences} up ON up.userid = u.id AND up.name = :name WHERE u.lastaccess > :timefrom AND u.lastaccess <= :now AND u.deleted = 0 $uservisibilityselect $groupselect"; } else { // Course level - show only enrolled users for now. // TODO: add a new capability for viewing of all users (guests+enrolled+viewing). list($esqljoin, $eparams) = get_enrolled_sql($context); $params = array_merge($params, $eparams); $sql = "SELECT $userfields $timeaccess $uservisibility FROM {user_lastaccess} ul $groupmembers, {user} u JOIN ($esqljoin) euj ON euj.id = u.id LEFT JOIN {user_preferences} up ON up.userid = u.id AND up.name = :name WHERE ul.timeaccess > :timefrom AND u.id = ul.userid AND ul.courseid = :courseid AND ul.timeaccess <= :now AND u.deleted = 0 $uservisibilityselect $groupselect $groupby ORDER BY lastaccess DESC"; $csql = "SELECT COUNT(u.id) FROM {user_lastaccess} ul $groupmembers, {user} u JOIN ($esqljoin) euj ON euj.id = u.id LEFT JOIN {user_preferences} up ON up.userid = u.id AND up.name = :name WHERE ul.timeaccess > :timefrom AND u.id = ul.userid AND ul.courseid = :courseid AND ul.timeaccess <= :now AND u.deleted = 0 $uservisibilityselect $groupselect"; $params['courseid'] = $courseid; } $this->sql = $sql; $this->csql = $csql; $this->params = $params; } /** * Get a list of the most recent online users * * @param int $userlimit The maximum number of users that will be returned (optional, unlimited if not set) * @return array */ public function get_users($userlimit = 0) { global $DB; $users = $DB->get_records_sql($this->sql, $this->params, 0, $userlimit); return $users; } /** * Count the number of online users * * @return int */ public function count_users() { global $DB; return $DB->count_records_sql($this->csql, $this->params); } } PK'9\SCC.online_users/amd/src/change_user_visibility.jsnu[// 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 . /** * A javascript module that handles the change of the user's visibility in the * online users block. * * @module block_online_users/change_user_visibility * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {getString} from 'core/str'; import Notification from 'core/notification'; import {setUserPreference} from 'core_user/repository'; /** * Selectors. * * @access private * @type {Object} */ const SELECTORS = { CHANGE_VISIBILITY_LINK: '#change-user-visibility', CHANGE_VISIBILITY_ICON: '#change-user-visibility .icon', }; /** * Change user visibility in the online users block. * * @method changeVisibility * @param {String} action * @param {String} userid * @returns {Promise} * @private */ const changeVisibility = (action, userid) => setUserPreference( 'block_online_users_uservisibility', action == "show" ? 1 : 0, userid, ) .then((data) => { if (data.saved) { const newAction = oppositeAction(action); changeVisibilityLinkAttr(newAction); changeVisibilityIconAttr(newAction); } return data; }).catch(Notification.exception); /** * Get the opposite action. * * @method oppositeAction * @param {String} action * @return {String} * @private */ const oppositeAction = (action) => action == 'show' ? 'hide' : 'show'; /** * Change the attribute values of the user visibility link in the online users block. * * @method changeVisibilityLinkAttr * @param {String} action * @returns {Promise} * @private */ const changeVisibilityLinkAttr = (action) => getTitle(action) .then((title) => { const link = document.querySelector(SELECTORS.CHANGE_VISIBILITY_LINK); link.dataset.action = action; link.title = title; return link; }); /** * Change the attribute values of the user visibility icon in the online users block. * * @method changeVisibilityIconAttr * @param {String} action * @returns {Promise} * @private */ const changeVisibilityIconAttr = (action) => getTitle(action) .then((title) => { const icon = document.querySelector(SELECTORS.CHANGE_VISIBILITY_ICON); // Add the proper title to the icon. icon.setAttribute('title', title); icon.setAttribute('aria-label', title); if (icon.closest("img")) { // If the icon is an image. icon.setAttribute('src', M.util.image_url(`t/${action}`)); icon.setAttribute('alt', title); } else { // Add the new icon class and remove the old one. icon.classList.add(getIconClass(action)); icon.classList.remove(getIconClass(oppositeAction(action))); } return title; }); /** * Get the proper class for the user visibility icon in the online users block. * * @method getIconClass * @param {String} action * @return {String} * @private */ const getIconClass = (action) => (action == 'show') ? 'fa-eye-slash' : 'fa-eye'; /** * Get the title description of the user visibility link in the online users block. * * @method getTitle * @param {String} action * @return {object} jQuery promise * @private */ const getTitle = (action) => getString(`online_status:${action}`, 'block_online_users'); /** * Initialise change user visibility function. * * @method init */ export const init = () => { document.addEventListener('click', (e) => { const link = e.target.closest(SELECTORS.CHANGE_VISIBILITY_LINK); if (!link) { return; } e.preventDefault(); changeVisibility( link.dataset.action, link.dataset.userid, ); }); }; PK'9\6z^DD8online_users/amd/build/change_user_visibility.min.js.mapnu[{"version":3,"file":"change_user_visibility.min.js","sources":["../src/change_user_visibility.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module that handles the change of the user's visibility in the\n * online users block.\n *\n * @module block_online_users/change_user_visibility\n * @copyright 2018 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getString} from 'core/str';\nimport Notification from 'core/notification';\nimport {setUserPreference} from 'core_user/repository';\n\n/**\n * Selectors.\n *\n * @access private\n * @type {Object}\n */\nconst SELECTORS = {\n CHANGE_VISIBILITY_LINK: '#change-user-visibility',\n CHANGE_VISIBILITY_ICON: '#change-user-visibility .icon',\n};\n\n/**\n * Change user visibility in the online users block.\n *\n * @method changeVisibility\n * @param {String} action\n * @param {String} userid\n * @returns {Promise}\n * @private\n */\nconst changeVisibility = (action, userid) => setUserPreference(\n 'block_online_users_uservisibility',\n action == \"show\" ? 1 : 0,\n userid,\n)\n.then((data) => {\n if (data.saved) {\n const newAction = oppositeAction(action);\n changeVisibilityLinkAttr(newAction);\n changeVisibilityIconAttr(newAction);\n }\n return data;\n}).catch(Notification.exception);\n\n/**\n * Get the opposite action.\n *\n * @method oppositeAction\n * @param {String} action\n * @return {String}\n * @private\n */\nconst oppositeAction = (action) => action == 'show' ? 'hide' : 'show';\n\n/**\n * Change the attribute values of the user visibility link in the online users block.\n *\n * @method changeVisibilityLinkAttr\n * @param {String} action\n * @returns {Promise}\n * @private\n */\nconst changeVisibilityLinkAttr = (action) => getTitle(action)\n .then((title) => {\n const link = document.querySelector(SELECTORS.CHANGE_VISIBILITY_LINK);\n link.dataset.action = action;\n link.title = title;\n return link;\n });\n\n/**\n * Change the attribute values of the user visibility icon in the online users block.\n *\n * @method changeVisibilityIconAttr\n * @param {String} action\n * @returns {Promise}\n * @private\n */\nconst changeVisibilityIconAttr = (action) => getTitle(action)\n .then((title) => {\n const icon = document.querySelector(SELECTORS.CHANGE_VISIBILITY_ICON);\n\n // Add the proper title to the icon.\n icon.setAttribute('title', title);\n icon.setAttribute('aria-label', title);\n\n if (icon.closest(\"img\")) {\n // If the icon is an image.\n icon.setAttribute('src', M.util.image_url(`t/${action}`));\n icon.setAttribute('alt', title);\n } else {\n // Add the new icon class and remove the old one.\n icon.classList.add(getIconClass(action));\n icon.classList.remove(getIconClass(oppositeAction(action)));\n }\n return title;\n });\n\n/**\n * Get the proper class for the user visibility icon in the online users block.\n *\n * @method getIconClass\n * @param {String} action\n * @return {String}\n * @private\n */\nconst getIconClass = (action) => (action == 'show') ? 'fa-eye-slash' : 'fa-eye';\n\n/**\n * Get the title description of the user visibility link in the online users block.\n *\n * @method getTitle\n * @param {String} action\n * @return {object} jQuery promise\n * @private\n */\nconst getTitle = (action) => getString(`online_status:${action}`, 'block_online_users');\n\n/**\n * Initialise change user visibility function.\n *\n * @method init\n */\nexport const init = () => {\n document.addEventListener('click', (e) => {\n const link = e.target.closest(SELECTORS.CHANGE_VISIBILITY_LINK);\n if (!link) {\n return;\n }\n e.preventDefault();\n changeVisibility(\n link.dataset.action,\n link.dataset.userid,\n );\n });\n};\n"],"names":["SELECTORS","oppositeAction","action","changeVisibilityLinkAttr","getTitle","then","title","link","document","querySelector","dataset","changeVisibilityIconAttr","icon","setAttribute","closest","M","util","image_url","classList","add","getIconClass","remove","addEventListener","e","target","userid","preventDefault","data","saved","newAction","catch","Notification","exception"],"mappings":";;;;;;;;4JAkCMA,iCACsB,0BADtBA,iCAEsB,gCAkCtBC,eAAkBC,QAAqB,QAAVA,OAAmB,OAAS,OAUzDC,yBAA4BD,QAAWE,SAASF,QACjDG,MAAMC,cACGC,KAAOC,SAASC,cAAcT,yCACpCO,KAAKG,QAAQR,OAASA,OACtBK,KAAKD,MAAQA,MACNC,QAWTI,yBAA4BT,QAAWE,SAASF,QACjDG,MAAMC,cACGM,KAAOJ,SAASC,cAAcT,yCAGpCY,KAAKC,aAAa,QAASP,OAC3BM,KAAKC,aAAa,aAAcP,OAE5BM,KAAKE,QAAQ,QAEbF,KAAKC,aAAa,MAAOE,EAAEC,KAAKC,sBAAef,UAC/CU,KAAKC,aAAa,MAAOP,SAGzBM,KAAKM,UAAUC,IAAIC,aAAalB,SAChCU,KAAKM,UAAUG,OAAOD,aAAanB,eAAeC,WAE/CI,SAWTc,aAAgBlB,QAAsB,QAAVA,OAAoB,eAAiB,SAUjEE,SAAYF,SAAW,0CAA2BA,QAAU,oCAO9C,KAChBM,SAASc,iBAAiB,SAAUC,UAC1BhB,KAAOgB,EAAEC,OAAOV,QAAQd,kCA/Fb,IAACE,OAAQuB,OAgGrBlB,OAGLgB,EAAEG,iBAnGgBxB,OAqGdK,KAAKG,QAAQR,OArGSuB,OAsGtBlB,KAAKG,QAAQe,QAtGoB,iCACzC,oCACU,QAAVvB,OAAmB,EAAI,EACvBuB,QAEHpB,MAAMsB,UACCA,KAAKC,MAAO,OACNC,UAAY5B,eAAeC,QACjCC,yBAAyB0B,WACzBlB,yBAAyBkB,kBAEtBF,QACRG,MAAMC,sBAAaC"}PK'9\9Lvv4online_users/amd/build/change_user_visibility.min.jsnu[define("block_online_users/change_user_visibility",["exports","core/str","core/notification","core_user/repository"],(function(_exports,_str,_notification,_repository){var obj; /** * A javascript module that handles the change of the user's visibility in the * online users block. * * @module block_online_users/change_user_visibility * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};const SELECTORS_CHANGE_VISIBILITY_LINK="#change-user-visibility",SELECTORS_CHANGE_VISIBILITY_ICON="#change-user-visibility .icon",oppositeAction=action=>"show"==action?"hide":"show",changeVisibilityLinkAttr=action=>getTitle(action).then((title=>{const link=document.querySelector(SELECTORS_CHANGE_VISIBILITY_LINK);return link.dataset.action=action,link.title=title,link})),changeVisibilityIconAttr=action=>getTitle(action).then((title=>{const icon=document.querySelector(SELECTORS_CHANGE_VISIBILITY_ICON);return icon.setAttribute("title",title),icon.setAttribute("aria-label",title),icon.closest("img")?(icon.setAttribute("src",M.util.image_url("t/".concat(action))),icon.setAttribute("alt",title)):(icon.classList.add(getIconClass(action)),icon.classList.remove(getIconClass(oppositeAction(action)))),title})),getIconClass=action=>"show"==action?"fa-eye-slash":"fa-eye",getTitle=action=>(0,_str.getString)("online_status:".concat(action),"block_online_users");_exports.init=()=>{document.addEventListener("click",(e=>{const link=e.target.closest(SELECTORS_CHANGE_VISIBILITY_LINK);var action,userid;link&&(e.preventDefault(),action=link.dataset.action,userid=link.dataset.userid,(0,_repository.setUserPreference)("block_online_users_uservisibility","show"==action?1:0,userid).then((data=>{if(data.saved){const newAction=oppositeAction(action);changeVisibilityLinkAttr(newAction),changeVisibilityIconAttr(newAction)}return data})).catch(_notification.default.exception))}))}})); //# sourceMappingURL=change_user_visibility.min.js.mapPK'9\ $online_users/tests/generator/lib.phpnu[. /** * block_online_users data generator * * @package block_online_users * @category test * @copyright 2012 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Online users block data generator class * * @package block_online_users * @category test * @copyright 2012 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_online_users_generator extends testing_block_generator { /** * Create (simulated) logged in users and add some of them to groups in a course */ public function create_logged_in_users() { global $DB; $generator = advanced_testcase::getDataGenerator(); $data = array(); // Create 2 courses. $course1 = $generator->create_course(); $data['course1'] = $course1; $course2 = $generator->create_course(); $data['course2'] = $course2; // Create 9 (simulated) logged in users enroled into $course1. for ($i = 1; $i <= 9; $i++) { $user = $generator->create_user(); $DB->set_field('user', 'lastaccess', time(), array('id' => $user->id)); $generator->enrol_user($user->id, $course1->id); $DB->insert_record('user_lastaccess', array('userid' => $user->id, 'courseid' => $course1->id, 'timeaccess' => time())); $data['user' . $i] = $user; } // Create 3 (simulated) logged in users who are not enroled into $course1. for ($i = 10; $i <= 12; $i++) { $user = $generator->create_user(); $DB->set_field('user', 'lastaccess', time(), array('id' => $user->id)); $data['user' . $i] = $user; } // Create 3 groups in course 1. $group1 = $generator->create_group(array('courseid' => $course1->id)); $data['group1'] = $group1; $group2 = $generator->create_group(array('courseid' => $course1->id)); $data['group2'] = $group2; $group3 = $generator->create_group(array('courseid' => $course1->id)); $data['group3'] = $group3; // Add 3 users to course group 1. $generator->create_group_member(array('groupid' => $group1->id, 'userid' => $data['user1']->id)); $generator->create_group_member(array('groupid' => $group1->id, 'userid' => $data['user2']->id)); $generator->create_group_member(array('groupid' => $group1->id, 'userid' => $data['user3']->id)); // Add 4 users to course group 2. $generator->create_group_member(array('groupid' => $group2->id, 'userid' => $data['user3']->id)); $generator->create_group_member(array('groupid' => $group2->id, 'userid' => $data['user4']->id)); $generator->create_group_member(array('groupid' => $group2->id, 'userid' => $data['user5']->id)); $generator->create_group_member(array('groupid' => $group2->id, 'userid' => $data['user6']->id)); return $data; // Return the user, course and group objects. } } PK'9\eOS99(online_users/tests/online_users_test.phpnu[. namespace block_online_users; /** * Online users testcase * * @package block_online_users * @category test * @copyright 2015 University of Nottingham * @author Barry Oosthuizen * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class online_users_test extends \advanced_testcase { protected $data; /** * Tests initial setup. * * Prepare the site with some courses, groups, users and * simulate various recent accesses. */ protected function setUp(): void { parent::setUp(); // Generate (simulated) recently logged-in users. $generator = $this->getDataGenerator()->get_plugin_generator('block_online_users'); $this->data = $generator->create_logged_in_users(); // Confirm we have modified the site and requires reset. $this->resetAfterTest(true); } /** * Check logged in group 1, 2 & 3 members in course 1 (should be 3, 4 and 0). * * @param array $data Array of user, course and group objects * @param int $now Current Unix timestamp * @param int $timetoshowusers The time window (in seconds) to check for the latest logged in users */ public function test_fetcher_course1_group_members(): void { global $CFG; $groupid = $this->data['group1']->id; $now = time(); $timetoshowusers = $CFG->block_online_users_timetosee * 60; $context = \context_course::instance($this->data['course1']->id); $courseid = $this->data['course1']->id; $onlineusers = new fetcher($groupid, $now, $timetoshowusers, $context, false, $courseid); $usercount = $onlineusers->count_users(); $users = $onlineusers->get_users(); $this->assertEquals(3, $usercount, 'There was a problem counting the number of online users in group 1'); $this->assertEquals($usercount, count($users), 'There was a problem counting the number of online users in group 1'); $groupid = $this->data['group2']->id; $onlineusers = new fetcher($groupid, $now, $timetoshowusers, $context, false, $courseid); $usercount = $onlineusers->count_users(); $users = $onlineusers->get_users(); $this->assertEquals($usercount, count($users), 'There was a problem counting the number of online users in group 2'); $this->assertEquals(4, $usercount, 'There was a problem counting the number of online users in group 2'); $groupid = $this->data['group3']->id; $onlineusers = new fetcher($groupid, $now, $timetoshowusers, $context, false, $courseid); $usercount = $onlineusers->count_users(); $users = $onlineusers->get_users(); $this->assertEquals($usercount, count($users), 'There was a problem counting the number of online users in group 3'); $this->assertEquals(0, $usercount, 'There was a problem counting the number of online users in group 3'); } /** * Check logged in users in courses 1 & 2 (should be 9 and 0). * * @param array $data Array of user, course and group objects * @param int $now Current Unix timestamp * @param int $timetoshowusers The time window (in seconds) to check for the latest logged in users */ public function test_fetcher_courses(): void { global $CFG; $currentgroup = null; $now = time(); $timetoshowusers = $CFG->block_online_users_timetosee * 60; $context = \context_course::instance($this->data['course1']->id); $courseid = $this->data['course1']->id; $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, false, $courseid); $usercount = $onlineusers->count_users(); $users = $onlineusers->get_users(); $this->assertEquals($usercount, count($users), 'There was a problem counting the number of online users in course 1'); $this->assertEquals(9, $usercount, 'There was a problem counting the number of online users in course 1'); $courseid = $this->data['course2']->id; $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, false, $courseid); $usercount = $onlineusers->count_users(); $users = $onlineusers->get_users(); $this->assertEquals($usercount, count($users), 'There was a problem counting the number of online users in course 2'); $this->assertEquals(0, $usercount, 'There was a problem counting the number of online users in course 2'); } /** * Check logged in at the site level (should be 12). * * @param int $now Current Unix timestamp * @param int $timetoshowusers The time window (in seconds) to check for the latest logged in users */ public function test_fetcher_sitelevel(): void { global $CFG; $currentgroup = null; $now = time(); $timetoshowusers = $CFG->block_online_users_timetosee * 60; $context = \context_system::instance(); $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, true); $usercount = $onlineusers->count_users(); $users = $onlineusers->get_users(); $this->assertEquals($usercount, count($users), 'There was a problem counting the number of online users at site level'); $this->assertEquals(12, $usercount, 'There was a problem counting the number of online users at site level'); } /** * Check user visibility setting for course group members. */ public function test_user_visibility_course1_group1_members(): void { global $CFG; // Enable users to set their visibility to others in the online users block. $CFG->block_online_users_onlinestatushiding = true; $groupid = $this->data['group1']->id; $now = time(); $timetoshowusers = $CFG->block_online_users_timetosee * 60; $context = \context_course::instance($this->data['course1']->id); $courseid = $this->data['course1']->id; $user1 = $this->data['user1']; $user2 = $this->data['user2']; // Set user2 as logged user. $this->setUser($user2); $onlineusers = new fetcher($groupid, $now, $timetoshowusers, $context, false, $courseid); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should be displayed in the online users block. $this->assertEquals(3, $usercount); $this->assertTrue(array_key_exists($user1->id, $users)); // Set user1 as logged user. $this->setUser($user1); // Set visibility to 'hide' for user1. set_user_preference('block_online_users_uservisibility', 0); // Test if the fetcher gets all the users including user1. $onlineusers = new fetcher($groupid, $now, $timetoshowusers, $context, false, $courseid); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should be displayed in the online users block. $this->assertEquals(3, $usercount); $this->assertTrue(array_key_exists($user1->id, $users)); // Set user2 as logged user. $this->setUser($user2); // Test if the fetcher gets all the users excluding user1. $onlineusers = new fetcher($groupid, $now, $timetoshowusers, $context, false, $courseid); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should not be displayed in the online users block. $this->assertEquals(2, $usercount); $this->assertFalse(array_key_exists($user1->id, $users)); // Disable users to set their visibility to others in the online users block. // All users should be displayed now and the visibility status of a users should be ignored, // as the capability of setting the visibility to other user has been disabled. $CFG->block_online_users_onlinestatushiding = false; // Test if the fetcher gets all the users including user1. $onlineusers = new fetcher($groupid, $now, $timetoshowusers, $context, false, $courseid); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should be displayed in the online users block. $this->assertEquals(3, $usercount); $this->assertTrue(array_key_exists($user1->id, $users)); } /** * Check user visibility setting at course level. */ public function test_user_visibility_courses(): void { global $CFG; $currentgroup = null; $now = time(); $timetoshowusers = $CFG->block_online_users_timetosee * 60; $context = \context_course::instance($this->data['course1']->id); $courseid = $this->data['course1']->id; $user1 = $this->data['user1']; $user2 = $this->data['user2']; // Set user2 as logged user. $this->setUser($user2); // Test if the fetcher gets all the users including user1. $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, false, $courseid); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should be displayed in the online users block. $this->assertEquals(9, $usercount); $this->assertTrue(array_key_exists($user1->id, $users)); // Set user1 as logged user. $this->setUser($user1); // Set visibility to 'hide' for user1. set_user_preference('block_online_users_uservisibility', 0); // Test if the fetcher gets all the users including user1. $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, false, $courseid); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should be displayed in the online users block. $this->assertEquals(9, $usercount); $this->assertTrue(array_key_exists($user1->id, $users)); // Set user2 as logged user. $this->setUser($user2); // Test if the fetcher gets all the users excluding user1. $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, false, $courseid); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should not be displayed in the online users block. $this->assertEquals(8, $usercount); $this->assertFalse(array_key_exists($user1->id, $users)); // Disable users to set their visibility to others in the online users block. // All users should be displayed now and the visibility status of a users should be ignored, // as the capability of setting the visibility to other user has been disabled. $CFG->block_online_users_onlinestatushiding = false; // Test if the fetcher gets all the users including user1. $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, false, $courseid); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should be displayed in the online users block. $this->assertEquals(9, $usercount); $this->assertTrue(array_key_exists($user1->id, $users)); } /** * Check user visibility setting at site level. */ public function test_user_visibility_sitelevel(): void { global $CFG; $currentgroup = null; $now = time(); $timetoshowusers = $CFG->block_online_users_timetosee * 60; $context = \context_system::instance(); $user1 = $this->data['user1']; $user2 = $this->data['user2']; // Set user2 as logged user. $this->setUser($user2); // Test if the fetcher gets all the users including user1. $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, true); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should be displayed in the online users block. $this->assertEquals(12, $usercount); $this->assertTrue(array_key_exists($user1->id, $users)); // Set user1 as logged user. $this->setUser($user1); // Set visibility to 'hide' for user1. set_user_preference('block_online_users_uservisibility', 0); // Test if the fetcher gets all the users including user1. $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, true); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should be displayed in the online users block. $this->assertEquals(12, $usercount); $this->assertTrue(array_key_exists($user1->id, $users)); // Set user2 as logged user. $this->setUser($user2); // Test if the fetcher gets all the users excluding user1. $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, true); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should not be displayed in the online users block. $this->assertEquals(11, $usercount); $this->assertFalse(array_key_exists($user1->id, $users)); // Disable users to set their visibility to others in the online users block. // All users should be displayed now and the visibility status of a users should be ignored, // as the capability of setting the visibility to other user has been disabled. $CFG->block_online_users_onlinestatushiding = false; // Test if the fetcher gets all the users including user1. $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $context, true); $users = $onlineusers->get_users(); $usercount = $onlineusers->count_users(); // User1 should be displayed in the online users block. $this->assertEquals(12, $usercount); $this->assertTrue(array_key_exists($user1->id, $users)); } } PK'9\- O%online_users/tests/generator_test.phpnu[. namespace block_online_users; /** * PHPUnit data generator testcase * * @package block_online_users * @category phpunit * @copyright 2012 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class generator_test extends \advanced_testcase { public function test_generator(): void { global $DB; $this->resetAfterTest(true); $beforeblocks = $DB->count_records('block_instances'); $beforecontexts = $DB->count_records('context'); /** @var \block_online_users_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('block_online_users'); $this->assertInstanceOf('block_online_users_generator', $generator); $this->assertEquals('online_users', $generator->get_blockname()); $generator->create_instance(); $generator->create_instance(); $bi = $generator->create_instance(); $this->assertEquals($beforeblocks+3, $DB->count_records('block_instances')); } } PK'9\؈dd:online_users/tests/behat/block_online_users_course.featurenu[@block @block_online_users Feature: The online users block allow you to see who is currently online In order to enable the online users block on an course page As a teacher I can add the online users block to a course page Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | Scenario: Add the online users on course page and see myself Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Online users" block Then I should see "Teacher 1" in the "Online users" "block" And I should see "1 online user" in the "Online users" "block" Scenario: Add the online users on course page and see other logged in users Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Online users" block And I log out And I log in as "student2" And I log out When I log in as "student1" And I am on "Course 1" course homepage Then I should see "Teacher 1" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" And I should not see "Student 2" in the "Online users" "block" And I should see "2 online users" in the "Online users" "block" @javascript Scenario: Hide/show user's online status from/to other users in the online users block on course page Given the following config values are set as admin: | block_online_users_onlinestatushiding | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Online users" block And I log out When I log in as "student1" And I am on "Course 1" course homepage Then "Hide" "icon" should exist in the "#change-user-visibility" "css_element" When I click on "#change-user-visibility" "css_element" And I wait "1" seconds Then "Show" "icon" should exist in the "#change-user-visibility" "css_element" And I log out When I log in as "teacher1" And I am on "Course 1" course homepage Then I should see "1 online user" in the "Online users" "block" And I should see "Teacher 1" in the "Online users" "block" And I should not see "Student 1" in the "Online users" "block" And I log out When I log in as "student1" And I am on "Course 1" course homepage Then "Show" "icon" should exist in the "#change-user-visibility" "css_element" When I click on "#change-user-visibility" "css_element" And I wait "1" seconds Then "Hide" "icon" should exist in the "#change-user-visibility" "css_element" And I log out When I log in as "teacher1" And I am on "Course 1" course homepage Then I should see "2 online users" in the "Online users" "block" And I should see "Teacher 1" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" @javascript Scenario: Hide/show icon is not visible in the online users block on course page when the setting is disabled Given the following config values are set as admin: | block_online_users_onlinestatushiding | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Online users" block And I log out And I log in as "student1" And I am on "Course 1" course homepage And "Hide" "icon" should exist in the ".block.block_online_users" "css_element" And I log out And the following config values are set as admin: | block_online_users_onlinestatushiding | 0 | When I log in as "student1" And I am on "Course 1" course homepage Then I should see "Student 1" in the "Online users" "block" And "Hide" "icon" should not exist in the ".block.block_online_users" "css_element" @javascript Scenario: User is displayed in the online users block on course page when visibility setting is disabled, ignoring the previously set visibility state Given the following config values are set as admin: | block_online_users_onlinestatushiding | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Online users" block And I log out And I log in as "student1" And I am on "Course 1" course homepage And "Hide" "icon" should exist in the "#change-user-visibility" "css_element" And I click on "#change-user-visibility" "css_element" And I wait "1" seconds And "Show" "icon" should exist in the "#change-user-visibility" "css_element" And I log out And the following config values are set as admin: | block_online_users_onlinestatushiding | 0 | And I log in as "teacher1" When I am on "Course 1" course homepage Then I should see "2 online users" in the "Online users" "block" And I should see "Teacher 1" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" PK'9\e=online_users/tests/behat/block_online_users_frontpage.featurenu[@block @block_online_users Feature: The online users block allow you to see who is currently online on frontpage There should be some commonality for the users to show up In order to enable the online users block on the frontpage As an admin I can add the online users block to the frontpage Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student2 | C1 | student | Scenario: View the online users block on the front page and see myself Given I log in as "admin" And I am on site homepage And I turn editing mode on When I add the "Online users" block Then I should see "Admin User" in the "Online users" "block" And I should see "1 online user" in the "Online users" "block" Scenario: View the online users block on the front page as a logged in user Given I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Online users" block And I log out And I log in as "student2" And I log out When I log in as "student1" And I am on site homepage Then I should not see "Admin User" in the "Online users" "block" And I should see "Other users (1)" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" And I should see "Student 2" in the "Online users" "block" And I should see "3 online users" in the "Online users" "block" Scenario: View the online users block on the front page as a guest Given I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Online users" block And I log out And I log in as "student2" And I log out And I log in as "student1" And I log out When I log in as "guest" And I am on site homepage Then I should not see "Admin User" in the "Online users" "block" And I should not see "Student 1" in the "Online users" "block" And I should not see "Student 2" in the "Online users" "block" And I should see "3 online users" in the "Online users" "block" @javascript Scenario: Hide/show user's online status from/to other users in the online users block on front page Given the following config values are set as admin: | block_online_users_onlinestatushiding | 1 | And I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Online users" block And I log out When I log in as "student1" And I am on site homepage Then "Hide" "icon" should exist in the "#change-user-visibility" "css_element" When I click on "#change-user-visibility" "css_element" And I wait "1" seconds Then "Show" "icon" should exist in the "#change-user-visibility" "css_element" And I log out When I log in as "student2" And I am on site homepage Then I should see "2 online user" in the "Online users" "block" And I should not see "Admin" in the "Online users" "block" And I should see "Other users (1)" in the "Online users" "block" And I should see "Student 2" in the "Online users" "block" And I should not see "Student 1" in the "Online users" "block" And I log out When I log in as "student1" And I am on site homepage Then "Show" "icon" should exist in the "#change-user-visibility" "css_element" When I click on "#change-user-visibility" "css_element" And I wait "1" seconds Then "Hide" "icon" should exist in the "#change-user-visibility" "css_element" And I log out When I log in as "student2" And I am on site homepage Then I should see "3 online users" in the "Online users" "block" And I should not see "Admin" in the "Online users" "block" And I should see "Other users (1)" in the "Online users" "block" And I should see "Student 2" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" @javascript Scenario: Hide/show icon is not visible in the online users block on front page when the setting is disabled Given the following config values are set as admin: | block_online_users_onlinestatushiding | 1 | And I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Online users" block And I log out And I log in as "student1" And I am on site homepage And "Hide" "icon" should exist in the ".block.block_online_users" "css_element" And I log out And the following config values are set as admin: | block_online_users_onlinestatushiding | 0 | When I log in as "student1" And I am on site homepage Then I should see "Student 1" in the "Online users" "block" And "Hide" "icon" should not exist in the ".block.block_online_users" "css_element" @javascript Scenario: User is displayed in the online users block on front page when visibility setting is disabled, ignoring the previously set visibility state Given the following config values are set as admin: | block_online_users_onlinestatushiding | 1 | And I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Online users" block And I log out And I log in as "student1" And I am on site homepage And "Hide" "icon" should exist in the "#change-user-visibility" "css_element" And I click on "#change-user-visibility" "css_element" And I wait "1" seconds And "Show" "icon" should exist in the "#change-user-visibility" "css_element" And I log out And I log in as "student2" And I am on site homepage And I should see "2 online user" in the "Online users" "block" And I should not see "Admin" in the "Online users" "block" And I should see "Other users (1)" in the "Online users" "block" And I should see "Student 2" in the "Online users" "block" And I should not see "Student 1" in the "Online users" "block" And I log out And the following config values are set as admin: | block_online_users_onlinestatushiding | 0 | And I log in as "student2" When I am on site homepage Then I should see "3 online users" in the "Online users" "block" And I should not see "Admin" in the "Online users" "block" And I should see "Other users (1)" in the "Online users" "block" And I should see "Student 2" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" PK'9\Nu<<=online_users/tests/behat/block_online_users_dashboard.featurenu[@block @block_online_users Feature: The online users block allow you to see who is currently online on dashboard There should be some commonality for the users to show up In order to use the online users block on the dashboard As a user I can view the online users block on my dashboard Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | online_users | System | 1 | my-index | side-post | Scenario: View the online users block on the dashboard and see myself Given I log in as "teacher1" Then I should see "Teacher 1" in the "Online users" "block" And I should see "1 online user" in the "Online users" "block" Scenario: View the online users block on the dashboard and see other logged in users Given I log in as "student2" And I log out And I log in as "student1" And I log out When I log in as "teacher1" Then I should see "Teacher 1" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" And I should see "Student 2" in the "Online users" "block" And I should see "3 online users" in the "Online users" "block" @javascript Scenario: Hide/show user's online status from/to other users in the online users block on dashboard Given the following config values are set as admin: | block_online_users_onlinestatushiding | 1 | And I log in as "student1" And I should see "1 online user" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" And "Hide" "icon" should exist in the "#change-user-visibility" "css_element" When I click on "#change-user-visibility" "css_element" And I wait "1" seconds Then "Show" "icon" should exist in the "#change-user-visibility" "css_element" And I log out When I log in as "student2" Then I should see "1 online user" in the "Online users" "block" And I should see "Student 2" in the "Online users" "block" And I should not see "Student 1" in the "Online users" "block" And I log out When I log in as "student1" Then "Show" "icon" should exist in the "#change-user-visibility" "css_element" When I click on "#change-user-visibility" "css_element" And I wait "1" seconds Then "Hide" "icon" should exist in the "#change-user-visibility" "css_element" And I log out When I log in as "student2" Then I should see "2 online users" in the "Online users" "block" And I should see "Student 2" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" @javascript Scenario: Hide/show icon is not visible in the online users block when the setting is disabled Given the following config values are set as admin: | block_online_users_onlinestatushiding | 1 | And I log in as "student1" And I should see "1 online user" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" And "Hide" "icon" should exist in the ".block.block_online_users" "css_element" And I log out And the following config values are set as admin: | block_online_users_onlinestatushiding | 0 | When I log in as "student1" Then I should see "1 online user" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" And "Hide" "icon" should not exist in the ".block.block_online_users" "css_element" @javascript Scenario: User is displayed in the online users block when visibility setting is disabled, ignoring the previously set visibility state Given the following config values are set as admin: | block_online_users_onlinestatushiding | 1 | And I log in as "student1" And I should see "1 online user" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" And "Hide" "icon" should exist in the "#change-user-visibility" "css_element" And I click on "#change-user-visibility" "css_element" And I wait "1" seconds And "Show" "icon" should exist in the "#change-user-visibility" "css_element" And I log out And I log in as "student2" And I should see "1 online user" in the "Online users" "block" And I should see "Student 2" in the "Online users" "block" And I should not see "Student 1" in the "Online users" "block" And I log out And the following config values are set as admin: | block_online_users_onlinestatushiding | 0 | When I log in as "student2" Then I should see "2 online users" in the "Online users" "block" And I should see "Student 2" in the "Online users" "block" And I should see "Student 1" in the "Online users" "block" PK'9\ѥ""#online_users/block_online_users.phpnu[. /** * Online users block. * * @package block_online_users * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use block_online_users\fetcher; /** * This block needs to be reworked. * The new roles system does away with the concepts of rigid student and * teacher roles. */ class block_online_users extends block_base { function init() { $this->title = get_string('pluginname','block_online_users'); } function has_config() { return true; } function get_content() { global $USER, $CFG, $DB, $OUTPUT; if ($this->content !== NULL) { return $this->content; } $this->content = new stdClass; $this->content->text = ''; $this->content->footer = ''; if (empty($this->instance)) { return $this->content; } $timetoshowusers = 300; //Seconds default if (isset($CFG->block_online_users_timetosee)) { $timetoshowusers = $CFG->block_online_users_timetosee * 60; } $now = time(); //Calculate if we are in separate groups $isseparategroups = ($this->page->course->groupmode == SEPARATEGROUPS && $this->page->course->groupmodeforce && !has_capability('moodle/site:accessallgroups', $this->page->context)); //Get the user current group $currentgroup = $isseparategroups ? groups_get_course_group($this->page->course) : NULL; $sitelevel = $this->page->course->id == SITEID || $this->page->context->contextlevel < CONTEXT_COURSE; $onlineusers = new fetcher($currentgroup, $now, $timetoshowusers, $this->page->context, $sitelevel, $this->page->course->id); //Calculate minutes $minutes = floor($timetoshowusers/60); $periodminutes = get_string('periodnminutes', 'block_online_users', $minutes); // Count users. $usercount = $onlineusers->count_users(); if ($usercount === 0) { $usercount = get_string('nouser', 'block_online_users'); } else if ($usercount === 1) { $usercount = get_string('numuser', 'block_online_users', $usercount); } else { $usercount = get_string('numusers', 'block_online_users', $usercount); } $this->content->text = '
            '.$usercount.' ('.$periodminutes.')
            '; // Verify if we can see the list of users, if not just print number of users. // If the current user is not logged in OR it's a guest then don't show any users. if (!has_capability('block/online_users:viewlist', $this->page->context) || isguestuser() || !isloggedin()) { return $this->content; } $userlimit = 50; // We'll just take the most recent 50 maximum. $initialcount = 0; if ($users = $onlineusers->get_users($userlimit)) { require_once($CFG->dirroot . '/user/lib.php'); $initialcount = count($users); foreach ($users as $user) { if (!user_can_view_profile($user)) { unset($users[$user->id]); continue; } $users[$user->id]->fullname = fullname($user); } } else { $users = array(); } //Now, we have in users, the list of users to show //Because they are online if (!empty($users)) { $this->page->requires->js_call_amd('block_online_users/change_user_visibility', 'init'); //Accessibility: Don't want 'Alt' text for the user picture; DO want it for the envelope/message link (existing lang string). //Accessibility: Converted
            to
              , inherit existing classes & styles. $this->content->text .= "
                \n"; if (isloggedin() && has_capability('moodle/site:sendmessage', $this->page->context) && !empty($CFG->messaging) && !isguestuser()) { $canshowicon = true; } else { $canshowicon = false; } foreach ($users as $user) { $this->content->text .= '
              • '; $timeago = format_time($now - $user->lastaccess); //bruno to calculate correctly on frontpage if (isguestuser($user)) { $this->content->text .= '
                '.$OUTPUT->user_picture($user, array('size'=>16, 'alttext'=>false)); $this->content->text .= get_string('guestuser').'
                '; } else { // Not a guest user. $this->content->text .= ''; if ($USER->id == $user->id) { if ($CFG->block_online_users_onlinestatushiding) { $action = ($user->uservisibility != null && $user->uservisibility == 0) ? 'show' : 'hide'; $anchortagcontents = $OUTPUT->pix_icon('t/' . $action, get_string('online_status:' . $action, 'block_online_users')); $anchortag = html_writer::link("", $anchortagcontents, array('title' => get_string('online_status:' . $action, 'block_online_users'), 'data-action' => $action, 'data-userid' => $user->id, 'id' => 'change-user-visibility')); $this->content->text .= '
                ' . $anchortag . '
                '; } } else { if ($canshowicon) { // Only when logged in and messaging active etc. $anchortagcontents = $OUTPUT->pix_icon('t/message', get_string('messageselectadd')); $anchorurl = new moodle_url('/message/index.php', array('id' => $user->id)); $anchortag = html_writer::link($anchorurl, $anchortagcontents, array('title' => get_string('messageselectadd'))); $this->content->text .= '
                '.$anchortag.'
                '; } } } $this->content->text .= "
              • \n"; } if ($initialcount - count($users) > 0) { $this->content->text .= '
              • '; $this->content->text .= html_writer::span( get_string('otherusers', 'block_online_users', $initialcount - count($users)) ); $this->content->text .= "
                "; $this->content->text .= "
              • \n"; } $this->content->text .= '
              '; } return $this->content; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { global $CFG; // Return all settings for all users since it is safe (no private keys, etc..). $configs = (object) [ 'timetosee' => $CFG->block_online_users_timetosee, 'onlinestatushiding' => $CFG->block_online_users_onlinestatushiding ]; return (object) [ 'instance' => new stdClass(), 'plugin' => $configs, ]; } } PK'9\INVĒonline_users/settings.phpnu[. /** * Online users block settings. * * @package block_online_users * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { $settings->add(new admin_setting_configtext('block_online_users_timetosee', get_string('timetosee', 'block_online_users'), get_string('configtimetosee', 'block_online_users'), 5, PARAM_INT)); $settings->add(new admin_setting_configcheckbox('block_online_users_onlinestatushiding', get_string('onlinestatushiding', 'block_online_users'), get_string('onlinestatushiding_desc', 'block_online_users'), 1)); } PK'9\%uuglobalsearch/styles.cssnu[.block_globalsearch .searchform { text-align: center; } .block_globalsearch .footer { text-align: center; } PK'9\ *globalsearch/db/access.phpnu[. /** * Global search Block caps. * * @package block_globalsearch * @copyright Prateek Sachan {@link http://prateeksachan.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/globalsearch:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/globalsearch:addinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\+globalsearch/lang/en/block_globalsearch.phpnu[. /** * Strings for component 'block_globalsearch'. * * @package block_globalsearch * @copyright Prateek Sachan {@link http://prateeksachan.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['globalsearch:addinstance'] = 'Add a new global search block'; $string['globalsearch:myaddinstance'] = 'Add a new global search block to Dashboard'; $string['pluginname'] = 'Global search'; $string['privacy:metadata'] = 'The Global search block only shows data stored in other locations.'; PK'9\33globalsearch/version.phpnu[. /** * Global Search version details. * * @package block_globalsearch * @copyright Prateek Sachan {@link http://prateeksachan.com} * @copyright Daniel Neis * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; $plugin->version = 2025041400; $plugin->requires = 2025040800; $plugin->component = 'block_globalsearch'; PK'9\G4)globalsearch/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_globalsearch. * * @package block_globalsearch * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_globalsearch\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_globalsearch implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\;6! ! #globalsearch/block_globalsearch.phpnu[. /** * Global search block. * * @package block_globalsearch * @copyright Prateek Sachan {@link http://prateeksachan.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Global search block. * * @package block_globalsearch * @copyright Prateek Sachan {@link http://prateeksachan.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_globalsearch extends block_base { /** * Initialises the block. * * @return void */ public function init() { $this->title = get_string('pluginname', 'block_globalsearch'); } /** * Gets the block contents. * * If we can avoid it better not check the server status here as connecting * to the server will slow down the whole page load. * * @return string The block HTML. */ public function get_content() { global $OUTPUT; if ($this->content !== null) { return $this->content; } $this->content = new stdClass(); $this->content->footer = ''; if (\core_search\manager::is_global_search_enabled() === false) { $this->content->text = get_string('globalsearchdisabled', 'search'); return $this->content; } $data = [ 'action' => new moodle_url('/search/index.php'), 'inputname' => 'q', 'searchstring' => get_string('search'), ]; if ($this->page->context && $this->page->context->contextlevel !== CONTEXT_SYSTEM) { $data['hiddenfields'] = (object) ['name' => 'context', 'value' => $this->page->context->id]; } $this->content->text = $OUTPUT->render_from_template('core/search_input', $data); return $this->content; } /** * This block shouldn't be added to a page if the global search advanced feature is disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { global $CFG; return $CFG->enableglobalsearch; } } PK'9\}  (globalsearch/tests/globalsearch_test.phpnu[. namespace block_globalsearch; use advanced_testcase; use block_globalsearch; use context_course; /** * PHPUnit block_globalsearch tests * * @package block_globalsearch * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_globalsearch */ final class globalsearch_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_globalsearch.php'); parent::setUpBeforeClass(); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_globalsearch(); // If global search advanced feature is enabled, the method should return true. set_config('enableglobalsearch', true); $this->assertTrue($block->can_block_be_added($page)); // However, if the global search advanced feature is disabled, the method should return false. set_config('enableglobalsearch', false); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\茒Iiisettings/styles.cssnu[.block_settings .block_tree ul { margin-left: 18px; } .block_settings .block_tree p.hasicon { text-indent: -21px; padding-left: 21px; } .block_settings .block_tree p.hasicon img { width: 16px; height: 16px; margin-top: 3px; margin-right: 5px; vertical-align: top; } .block_settings .block_tree p.hasicon.visibleifjs { display: block; } .block_settings .block_tree .tree_item.branch { padding-left: 21px; } .block_settings .block_tree .tree_item { cursor: pointer; margin: 3px 0; background-position: 0 50%; background-repeat: no-repeat; } .block_settings .block_tree .active_tree_node { font-weight: bold; } .block_settings .block_tree [aria-expanded="true"] { background-image: url('[[pix:t/expanded]]'); } .block_settings .block_tree [aria-expanded="false"] { background-image: url('[[pix:t/collapsed]]'); } .block_settings .block_tree [aria-expanded="true"].emptybranch { background-image: url('[[pix:t/collapsed_empty]]'); } .block_settings .block_tree [aria-expanded="false"] p.loading { background-image: url('[[pix:i/loading_small]]'); } /*rtl:raw: .block_settings .block_tree [aria-expanded="false"] {background-image: url('[[pix:t/collapsed_rtl]]');} .block_settings .block_tree [aria-expanded="true"].emptybranch {background-image: url('[[pix:t/collapsed_empty_rtl]]');} .block_settings .block_tree [aria-expanded="false"].loading {background-image: url('[[pix:i/loading_small]]');} */ .block_settings .block_tree [aria-hidden="false"] { display: block; } .block_settings .block_tree [aria-hidden="true"]:not(.icon) { display: none; } PK'9\ /settings/renderer.phpnu[. /** * Settings block * * @package block_settings * @copyright 2010 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_settings_renderer extends plugin_renderer_base { public function settings_tree(settings_navigation $navigation) { $count = 0; foreach ($navigation->children as &$child) { $child->preceedwithhr = ($count!==0); if ($child->display) { $count++; } } $navigationattrs = array( 'class' => 'block_tree list', 'role' => 'tree', 'data-ajax-loader' => 'block_navigation/site_admin_loader'); $content = $this->navigation_node($navigation, $navigationattrs); if (isset($navigation->id) && !is_numeric($navigation->id) && !empty($content)) { $content = $this->output->box($content, 'block_tree_box', $navigation->id); } return $content; } /** * Build the navigation node. * * @param navigation_node $node the navigation node object. * @param array $attrs list of attributes. * @param int $depth the depth, default to 1. * @return string the navigation node code. */ protected function navigation_node(navigation_node $node, $attrs=array(), $depth = 1) { $items = $node->children; // exit if empty, we don't want an empty ul element if ($items->count()==0) { return ''; } // array of nested li elements $lis = array(); $number = 0; foreach ($items as $item) { $number++; if (!$item->display) { continue; } $isbranch = ($item->children->count()>0 || $item->nodetype==navigation_node::NODETYPE_BRANCH); if ($isbranch) { $item->hideicon = true; } $content = $this->output->render($item); $id = $item->id ? $item->id : html_writer::random_id(); $ulattr = ['id' => $id . '_group', 'role' => 'group']; $liattr = ['class' => [$item->get_css_type(), 'depth_'.$depth], 'tabindex' => '-1', 'role' => 'treeitem']; $pattr = ['class' => ['tree_item']]; $pattr += !empty($item->id) ? ['id' => $item->id] : []; $hasicon = (!$isbranch && $item->icon instanceof renderable); if ($isbranch) { $liattr['class'][] = 'contains_branch'; if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count() == 0 && $item->nodetype == navigation_node::NODETYPE_BRANCH)) { $liattr += ['aria-expanded' => 'false']; } else { $liattr += ['aria-expanded' => 'true']; } if ($item->requiresajaxloading) { $liattr['data-requires-ajax'] = 'true'; $liattr['data-loaded'] = 'false'; } else { $liattr += ['aria-owns' => $id . '_group']; } } else if ($hasicon) { $liattr['class'][] = 'item_with_icon'; $pattr['class'][] = 'hasicon'; } if ($item->isactive === true) { $liattr['class'][] = 'current_branch'; } if (!empty($item->classes) && count($item->classes) > 0) { $pattr['class'] = array_merge($pattr['class'], $item->classes); } // class attribute on the div item which only contains the item content $pattr['class'][] = 'tree_item'; if ($isbranch) { $pattr['class'][] = 'branch'; } else { $pattr['class'][] = 'leaf'; } $liattr['class'] = join(' ', $liattr['class']); $pattr['class'] = join(' ', $pattr['class']); if (isset($liattr['aria-expanded']) && $liattr['aria-expanded'] === 'false') { $ulattr += ['aria-hidden' => 'true']; } $content = html_writer::tag('p', $content, $pattr) . $this->navigation_node($item, $ulattr, $depth + 1); if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) { $content = html_writer::empty_tag('hr') . $content; } $content = html_writer::tag('li', $content, $liattr); $lis[] = $content; } if (count($lis)) { if (empty($attrs['role'])) { $attrs['role'] = 'group'; } return html_writer::tag('ul', implode("\n", $lis), $attrs); } else { return ''; } } public function search_form(moodle_url $formtarget, $searchvalue) { $data = [ 'action' => $formtarget, 'inputname' => 'query', 'searchstring' => get_string('searchinsettings', 'admin'), 'query' => $searchvalue ]; return $this->render_from_template('core/search_input', $data); } } PK'9\7?  settings/db/access.phpnu[. /** * Settings block caps. * * @package block_settings * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/settings:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/settings:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\qˁ settings/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the settings block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @since Moodle 2.0 * @package block_settings * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * As of the implementation of this block and the general navigation code * in Moodle 2.0 the body of immediate upgrade work for this block and * settings is done in core upgrade {@see lib/db/upgrade.php} * * There were several reasons that they were put there and not here, both becuase * the process for the two blocks was very similar and because the upgrade process * was complex due to us wanting to remvoe the outmoded blocks that this * block was going to replace. * * @param int $oldversion * @param object $block */ function xmldb_block_settings_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\s|s#settings/lang/en/block_settings.phpnu[. /** * This file contains language strings used in the settings navigation block * * @since Moodle 2.0 * @package block_settings * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['enabledock'] = 'Allow the user to dock this block'; $string['pluginname'] = 'Administration'; $string['settings:addinstance'] = 'Add a new administration block'; $string['settings:myaddinstance'] = 'Add a new administration block to Dashboard'; $string['privacy:metadata'] = 'The Administration block only shows data stored in other locations.'; PK'9\|jsettings/edit_form.phpnu[. /** * Form for editing settings navigation instances. * * @since Moodle 2.0 * @package block_settings * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Form for setting navigation instances. * * @package block_settings * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_settings_edit_form extends block_edit_form { protected function specific_definition($mform) { $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $yesnooptions = array('yes'=>get_string('yes'), 'no'=>get_string('no')); $mform->addElement('select', 'config_enabledock', get_string('enabledock', $this->block->blockname), $yesnooptions); if (empty($this->block->config->enabledock) || $this->block->config->enabledock=='yes') { $mform->getElement('config_enabledock')->setSelected('yes'); } else { $mform->getElement('config_enabledock')->setSelected('no'); } } }PK'9\GL?settings/block_settings.phpnu[. /** * This file contains classes used to manage the navigation structures in Moodle * and was introduced as part of the changes occuring in Moodle 2.0 * * @since Moodle 2.0 * @package block_settings * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * The settings navigation tree block class * * Used to produce the settings navigation block new to Moodle 2.0 * * @package block_settings * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_settings extends block_base { /** @var string */ public static $navcount; public $blockname = null; /** @var bool */ protected $contentgenerated = false; /** @var bool|null */ protected $docked = null; /** * Set the initial properties for the block */ function init() { $this->blockname = get_class($this); $this->title = get_string('pluginname', $this->blockname); } /** * All multiple instances of this block * @return bool Returns true */ function instance_allow_multiple() { return false; } /** * The settings block cannot be hidden by default as it is integral to * the navigation of Moodle. * * @return false */ function instance_can_be_hidden() { return false; } /** * Set the applicable formats for this block to all * @return array */ function applicable_formats() { return array('all' => true); } /** * Allow the user to configure a block instance * @return bool Returns true */ function instance_allow_config() { return true; } function instance_can_be_docked() { return (parent::instance_can_be_docked() && (empty($this->config->enabledock) || $this->config->enabledock=='yes')); } function get_required_javascript() { $adminnode = $this->page->settingsnav->find('siteadministration', navigation_node::TYPE_SITE_ADMIN); parent::get_required_javascript(); $arguments = array( 'instanceid' => $this->instance->id, 'adminnodeid' => $adminnode ? $adminnode->id : null ); $this->page->requires->js_call_amd('block_settings/settingsblock', 'init', $arguments); } /** * Gets the content for this block by grabbing it from $this->page */ function get_content() { global $CFG, $OUTPUT; // First check if we have already generated, don't waste cycles if ($this->contentgenerated === true) { return true; } // JS for navigation moved to the standard theme, the code will probably have to depend on the actual page structure // $this->page->requires->js('/lib/javascript-navigation.js'); block_settings::$navcount++; // Check if this block has been docked if ($this->docked === null) { $this->docked = get_user_preferences('nav_in_tab_panel_settingsnav'.block_settings::$navcount, 0); } // Check if there is a param to change the docked state if ($this->docked && optional_param('undock', null, PARAM_INT)==$this->instance->id) { unset_user_preference('nav_in_tab_panel_settingsnav'.block_settings::$navcount, 0); $url = $this->page->url; $url->remove_params(array('undock')); redirect($url); } else if (!$this->docked && optional_param('dock', null, PARAM_INT)==$this->instance->id) { set_user_preferences(array('nav_in_tab_panel_settingsnav'.block_settings::$navcount=>1)); $url = $this->page->url; $url->remove_params(array('dock')); redirect($url); } $renderer = $this->page->get_renderer('block_settings'); $this->content = new stdClass(); $this->content->text = $renderer->settings_tree($this->page->settingsnav); // only do search if you have moodle/site:config if (!empty($this->content->text)) { if (has_capability('moodle/site:config',context_system::instance()) ) { $this->content->footer = $renderer->search_form(new moodle_url("$CFG->wwwroot/$CFG->admin/search.php"), optional_param('query', '', PARAM_RAW)); } else { $this->content->footer = ''; } } $this->contentgenerated = true; return true; } /** * Returns the role that best describes the settings block. * * @return string 'navigation' */ public function get_aria_role() { return 'navigation'; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = !empty($this->config) ? $this->config : new stdClass(); return (object) [ 'instance' => $configs, 'plugin' => new stdClass(), ]; } } PK'9\#settings/version.phpnu[. /** * Version details * * @package block_settings * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_settings'; // Full name of the plugin (used for diagnostics) PK'9\%settings/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_settings. * * @package block_settings * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_settings\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_settings implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\!settings/amd/src/settingsblock.jsnu[// 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 . /** * Load the settings block tree javscript * * @module block_settings/settingsblock * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {notifyBlockContentUpdated} from 'core_block/events'; import Tree from 'core/tree'; export const init = (instanceId, siteAdminNodeId) => { const adminTree = new Tree(".block_settings .block_tree"); const blockNode = document.querySelector(`[data-instance-id="${instanceId}"]`); if (siteAdminNodeId) { const siteAdminLink = adminTree.treeRoot.get(0).querySelector(`#${siteAdminNodeId} a`); const newContainer = document.createElement('span'); newContainer.setAttribute('tabindex', '0'); siteAdminLink.childNodes.forEach(node => newContainer.appendChild(node)); siteAdminLink.replaceWith(newContainer); } /** * The method to call when then the navtree finishes expanding a group. * * @method finishExpandingGroup * @param {Object} item * @fires event:blockContentUpdated */ adminTree.finishExpandingGroup = function(item) { Tree.prototype.finishExpandingGroup.call(adminTree, item); notifyBlockContentUpdated(blockNode); }; /** * The method to call whe then the navtree collapses a group * * @method collapseGroup * @param {Object} item * @fires event:blockContentUpdated */ adminTree.collapseGroup = function(item) { Tree.prototype.collapseGroup.call(adminTree, item); notifyBlockContentUpdated(blockNode); }; }; PK'9\-[V^ ^ +settings/amd/build/settingsblock.min.js.mapnu[{"version":3,"file":"settingsblock.min.js","sources":["../src/settingsblock.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Load the settings block tree javscript\n *\n * @module block_settings/settingsblock\n * @copyright 2015 John Okely \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {notifyBlockContentUpdated} from 'core_block/events';\nimport Tree from 'core/tree';\n\nexport const init = (instanceId, siteAdminNodeId) => {\n const adminTree = new Tree(\".block_settings .block_tree\");\n const blockNode = document.querySelector(`[data-instance-id=\"${instanceId}\"]`);\n\n if (siteAdminNodeId) {\n const siteAdminLink = adminTree.treeRoot.get(0).querySelector(`#${siteAdminNodeId} a`);\n const newContainer = document.createElement('span');\n newContainer.setAttribute('tabindex', '0');\n siteAdminLink.childNodes.forEach(node => newContainer.appendChild(node));\n siteAdminLink.replaceWith(newContainer);\n }\n\n /**\n * The method to call when then the navtree finishes expanding a group.\n *\n * @method finishExpandingGroup\n * @param {Object} item\n * @fires event:blockContentUpdated\n */\n adminTree.finishExpandingGroup = function(item) {\n Tree.prototype.finishExpandingGroup.call(adminTree, item);\n notifyBlockContentUpdated(blockNode);\n };\n\n /**\n * The method to call whe then the navtree collapses a group\n *\n * @method collapseGroup\n * @param {Object} item\n * @fires event:blockContentUpdated\n */\n adminTree.collapseGroup = function(item) {\n Tree.prototype.collapseGroup.call(adminTree, item);\n notifyBlockContentUpdated(blockNode);\n };\n};\n"],"names":["instanceId","siteAdminNodeId","adminTree","Tree","blockNode","document","querySelector","siteAdminLink","treeRoot","get","newContainer","createElement","setAttribute","childNodes","forEach","node","appendChild","replaceWith","finishExpandingGroup","item","prototype","call","collapseGroup"],"mappings":";;;;;;;oJAyBoB,CAACA,WAAYC,yBACvBC,UAAY,IAAIC,cAAK,+BACrBC,UAAYC,SAASC,2CAAoCN,qBAE3DC,gBAAiB,OACXM,cAAgBL,UAAUM,SAASC,IAAI,GAAGH,yBAAkBL,uBAC5DS,aAAeL,SAASM,cAAc,QAC5CD,aAAaE,aAAa,WAAY,KACtCL,cAAcM,WAAWC,SAAQC,MAAQL,aAAaM,YAAYD,QAClER,cAAcU,YAAYP,cAU9BR,UAAUgB,qBAAuB,SAASC,oBACjCC,UAAUF,qBAAqBG,KAAKnB,UAAWiB,4CAC1Bf,YAU9BF,UAAUoB,cAAgB,SAASH,oBAC1BC,UAAUE,cAAcD,KAAKnB,UAAWiB,4CACnBf"}PK'9\]:MCC'settings/amd/build/settingsblock.min.jsnu[define("block_settings/settingsblock",["exports","core_block/events","core/tree"],(function(_exports,_events,_tree){var obj; /** * Load the settings block tree javscript * * @module block_settings/settingsblock * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_tree=(obj=_tree)&&obj.__esModule?obj:{default:obj};_exports.init=(instanceId,siteAdminNodeId)=>{const adminTree=new _tree.default(".block_settings .block_tree"),blockNode=document.querySelector('[data-instance-id="'.concat(instanceId,'"]'));if(siteAdminNodeId){const siteAdminLink=adminTree.treeRoot.get(0).querySelector("#".concat(siteAdminNodeId," a")),newContainer=document.createElement("span");newContainer.setAttribute("tabindex","0"),siteAdminLink.childNodes.forEach((node=>newContainer.appendChild(node))),siteAdminLink.replaceWith(newContainer)}adminTree.finishExpandingGroup=function(item){_tree.default.prototype.finishExpandingGroup.call(adminTree,item),(0,_events.notifyBlockContentUpdated)(blockNode)},adminTree.collapseGroup=function(item){_tree.default.prototype.collapseGroup.call(adminTree,item),(0,_events.notifyBlockContentUpdated)(blockNode)}}})); //# sourceMappingURL=settingsblock.min.js.mapPK'9\Ή11classes/external.phpnu[. /** * Blocks external API * * @package core_block * @category external * @copyright 2017 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.3 */ use core_external\external_api; use core_external\external_files; use core_external\external_format_value; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; defined('MOODLE_INTERNAL') || die; require_once("$CFG->dirroot/my/lib.php"); /** * Blocks external functions * * @package core_block * @category external * @copyright 2015 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.3 */ class core_block_external extends external_api { /** * Returns a block structure. * * @return external_single_structure a block single structure. * @since Moodle 3.6 */ private static function get_block_structure() { return new external_single_structure( array( 'instanceid' => new external_value(PARAM_INT, 'Block instance id.'), 'name' => new external_value(PARAM_PLUGIN, 'Block name.'), 'region' => new external_value(PARAM_ALPHANUMEXT, 'Block region.'), 'positionid' => new external_value(PARAM_INT, 'Position id.'), 'collapsible' => new external_value(PARAM_BOOL, 'Whether the block is collapsible.'), 'dockable' => new external_value(PARAM_BOOL, 'Whether the block is dockable.'), 'weight' => new external_value(PARAM_INT, 'Used to order blocks within a region.', VALUE_OPTIONAL), 'visible' => new external_value(PARAM_BOOL, 'Whether the block is visible.', VALUE_OPTIONAL), 'contents' => new external_single_structure( array( 'title' => new external_value(PARAM_RAW, 'Block title.'), 'content' => new external_value(PARAM_RAW, 'Block contents.'), 'contentformat' => new external_format_value('content'), 'footer' => new external_value(PARAM_RAW, 'Block footer.'), 'files' => new external_files('Block files.'), ), 'Block contents (if required).', VALUE_OPTIONAL ), 'configs' => new external_multiple_structure( new external_single_structure( array( 'name' => new external_value(PARAM_RAW, 'Name.'), 'value' => new external_value(PARAM_RAW, 'JSON encoded representation of the config value.'), 'type' => new external_value(PARAM_ALPHA, 'Type (instance or plugin).'), ) ), 'Block instance and plugin configuration settings.', VALUE_OPTIONAL ), ), 'Block information.' ); } /** * Convenience function for getting all the blocks of the current $PAGE. * * @param bool $includeinvisible Whether to include not visible blocks or not * @param bool $returncontents Whether to return the block contents * @return array Block information * @since Moodle 3.6 */ private static function get_all_current_page_blocks($includeinvisible = false, $returncontents = false) { global $PAGE, $OUTPUT; // Set page URL to a fake URL to avoid errors. $PAGE->set_url(new \moodle_url('/webservice/core_block_external/')); // Load the block instances for all the regions. $PAGE->blocks->load_blocks($includeinvisible); $PAGE->blocks->create_all_block_instances(); $allblocks = array(); $blocks = $PAGE->blocks->get_content_for_all_regions($OUTPUT); foreach ($blocks as $region => $regionblocks) { $regioninstances = $PAGE->blocks->get_blocks_for_region($region); // Index block instances to retrieve required info. $blockinstances = array(); foreach ($regioninstances as $ri) { $blockinstances[$ri->instance->id] = $ri; } foreach ($regionblocks as $bc) { $block = [ 'instanceid' => $bc->blockinstanceid, 'name' => $blockinstances[$bc->blockinstanceid]->instance->blockname, 'region' => $region, 'positionid' => $bc->blockpositionid, 'collapsible' => (bool) $bc->collapsible, 'dockable' => (bool) $bc->dockable, 'weight' => $blockinstances[$bc->blockinstanceid]->instance->weight, 'visible' => $blockinstances[$bc->blockinstanceid]->instance->visible, ]; if ($returncontents) { $block['contents'] = (array) $blockinstances[$bc->blockinstanceid]->get_content_for_external($OUTPUT); } $configs = (array) $blockinstances[$bc->blockinstanceid]->get_config_for_external(); foreach ($configs as $type => $data) { foreach ((array) $data as $name => $value) { $block['configs'][] = [ 'name' => $name, 'value' => json_encode($value), // Always JSON encode, we may receive non-scalar values. 'type' => $type, ]; } } $allblocks[] = $block; } } return $allblocks; } /** * Returns description of get_course_blocks parameters. * * @return external_function_parameters * @since Moodle 3.3 */ public static function get_course_blocks_parameters() { return new external_function_parameters( array( 'courseid' => new external_value(PARAM_INT, 'course id'), 'returncontents' => new external_value(PARAM_BOOL, 'Whether to return the block contents.', VALUE_DEFAULT, false), ) ); } /** * Returns blocks information for a course. * * @param int $courseid The course id * @param bool $returncontents Whether to return the block contents * @return array Blocks list and possible warnings * @throws moodle_exception * @since Moodle 3.3 */ public static function get_course_blocks($courseid, $returncontents = false) { global $PAGE; $warnings = array(); $params = self::validate_parameters(self::get_course_blocks_parameters(), ['courseid' => $courseid, 'returncontents' => $returncontents]); $course = get_course($params['courseid']); $context = context_course::instance($course->id); self::validate_context($context); // Specific layout for frontpage course. if ($course->id == SITEID) { $PAGE->set_pagelayout('frontpage'); $PAGE->set_pagetype('site-index'); } else { $PAGE->set_pagelayout('course'); // Ensure course format is set (view course/view.php). $course->format = course_get_format($course)->get_format(); $PAGE->set_pagetype('course-view-' . $course->format); } $allblocks = self::get_all_current_page_blocks(false, $params['returncontents']); return array( 'blocks' => $allblocks, 'warnings' => $warnings ); } /** * Returns description of get_course_blocks result values. * * @return external_single_structure * @since Moodle 3.3 */ public static function get_course_blocks_returns() { return new external_single_structure( array( 'blocks' => new external_multiple_structure(self::get_block_structure(), 'List of blocks in the course.'), 'warnings' => new external_warnings(), ) ); } /** * Returns description of get_dashboard_blocks parameters. * * @return external_function_parameters * @since Moodle 3.6 */ public static function get_dashboard_blocks_parameters() { return new external_function_parameters( array( 'userid' => new external_value(PARAM_INT, 'User id (optional), default is current user.', VALUE_DEFAULT, 0), 'returncontents' => new external_value(PARAM_BOOL, 'Whether to return the block contents.', VALUE_DEFAULT, false), 'mypage' => new external_value(PARAM_TEXT, 'What my page to return blocks of', VALUE_DEFAULT, MY_PAGE_DEFAULT), ) ); } /** * Returns blocks information for the given user dashboard. * * @param int $userid The user id to retrieve the blocks from, optional, default is to current user. * @param bool $returncontents Whether to return the block contents * @param string $mypage The page to get blocks of within my * @return array Blocks list and possible warnings * @throws moodle_exception * @since Moodle 3.6 */ public static function get_dashboard_blocks($userid = 0, $returncontents = false, $mypage = MY_PAGE_DEFAULT) { global $CFG, $USER, $PAGE; require_once($CFG->dirroot . '/my/lib.php'); $warnings = array(); $params = self::validate_parameters(self::get_dashboard_blocks_parameters(), ['userid' => $userid, 'returncontents' => $returncontents, 'mypage' => $mypage]); $userid = $params['userid']; if (empty($userid)) { $userid = $USER->id; } if ($USER->id != $userid) { // We must check if the current user can view other users dashboard. require_capability('moodle/site:config', context_system::instance()); $user = core_user::get_user($userid, '*', MUST_EXIST); core_user::require_active_user($user); } $context = context_user::instance($userid);; self::validate_context($context); $currentpage = null; if ($params['mypage'] === MY_PAGE_DEFAULT) { $currentpage = my_get_page($userid); } else if ($params['mypage'] === MY_PAGE_COURSES) { $currentpage = my_get_page($userid, MY_PAGE_PUBLIC, MY_PAGE_COURSES); } if (!$currentpage) { throw new moodle_exception('mymoodlesetup'); } $PAGE->set_context($context); $PAGE->set_pagelayout('mydashboard'); $PAGE->set_pagetype('my-index'); $PAGE->blocks->add_region('content'); // Need to add this special regition to retrieve the central blocks. $PAGE->set_subpage($currentpage->id); // Load the block instances in the current $PAGE for all the regions. $returninvisible = has_capability('moodle/my:manageblocks', $context) ? true : false; $allblocks = self::get_all_current_page_blocks($returninvisible, $params['returncontents']); return array( 'blocks' => $allblocks, 'warnings' => $warnings ); } /** * Returns description of get_dashboard_blocks result values. * * @return external_single_structure * @since Moodle 3.6 */ public static function get_dashboard_blocks_returns() { return new external_single_structure( array( 'blocks' => new external_multiple_structure(self::get_block_structure(), 'List of blocks in the dashboard.'), 'warnings' => new external_warnings(), ) ); } } PK'9\Jl)classes/external/fetch_addable_blocks.phpnu[. namespace core_block\external; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; /** * This is the external method used for fetching the addable blocks in a given page. * * @package core_block * @since Moodle 3.11 * @copyright 2020 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class fetch_addable_blocks extends external_api { /** * Describes the parameters for execute. * * @return external_function_parameters */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters( [ 'pagecontextid' => new external_value(PARAM_INT, 'The context ID of the page.'), 'pagetype' => new external_value(PARAM_ALPHANUMEXT, 'The type of the page.'), 'pagelayout' => new external_value(PARAM_ALPHA, 'The layout of the page.'), 'subpage' => new external_value(PARAM_TEXT, 'The subpage identifier', VALUE_DEFAULT, ''), 'pagehash' => new external_value(PARAM_ALPHANUMEXT, 'Page hash', VALUE_DEFAULT, ''), ] ); } /** * Fetch the addable blocks in a given page. * * @param int $pagecontextid The context ID of the page * @param string $pagetype The type of the page * @param string $pagelayout The layout of the page * @param string $subpage The subpage identifier * @param string $pagehash Page hash that can be provided instead of all parameters above * @return array The blocks list */ public static function execute(int $pagecontextid, string $pagetype, string $pagelayout, string $subpage = '', string $pagehash = ''): array { global $PAGE; $params = self::validate_parameters(self::execute_parameters(), [ 'pagecontextid' => $pagecontextid, 'pagetype' => $pagetype, 'pagelayout' => $pagelayout, 'subpage' => $subpage, 'pagehash' => $pagehash, ] ); if ($params['pagehash']) { // If pagehash is specified, all other parameters are ignored, all information // about the page is stored in the session. $page = \moodle_page::retrieve_edited_page($params['pagehash'], MUST_EXIST); self::validate_context($page->context); } else { // For backward-compatibility and Mobile App instead of pagehash // we can specify context, pagelayout, pagetype and subtype. $context = \context::instance_by_id($params['pagecontextid']); // Validate the context. This will also set the context in $PAGE. self::validate_context($context); // We need to manually set the page layout and page type. $PAGE->set_pagelayout($params['pagelayout']); $PAGE->set_pagetype($params['pagetype']); $PAGE->set_subpage($params['subpage']); $page = $PAGE; } // Firstly, we need to load all currently existing page blocks to later determine which blocks are addable. $page->blocks->load_blocks(true); $page->blocks->create_all_block_instances(); $addableblocks = $page->blocks->get_addable_blocks(); return array_map(function($block) use ($page) { $classname = \block_manager::get_block_edit_form_class($block->name); return [ 'name' => $block->name, 'title' => get_string('pluginname', "block_{$block->name}"), 'blockform' => $classname::display_form_when_adding() ? $classname : null, ]; }, $addableblocks); } /** * Describes the execute return value. * * @return external_multiple_structure */ public static function execute_returns(): external_multiple_structure { return new external_multiple_structure( new external_single_structure( [ 'name' => new external_value(PARAM_PLUGIN, 'The name of the block.'), 'title' => new external_value(PARAM_RAW, 'The title of the block.'), 'blockform' => new external_value(PARAM_RAW, 'If this block type has a form when it is being added then the classname of the form') ] ), 'List of addable blocks in a given page.' ); } } PK'9\Ѩc((classes/privacy/provider.phpnu[. /** * Data provider. * * @package core_block * @copyright 2018 Frédéric Massart * @author Frédéric Massart * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_block\privacy; defined('MOODLE_INTERNAL') || die(); use context; use context_block; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; /** * Data provider class. * * @package core_block * @copyright 2018 Frédéric Massart * @author Frédéric Massart * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\subsystem\provider, \core_privacy\local\request\user_preference_provider, \core_privacy\local\request\core_userlist_provider { /** * Returns metadata. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { $collection->add_user_preference('blockIDhidden', 'privacy:metadata:userpref:hiddenblock'); $collection->add_user_preference('docked_block_instance_ID', 'privacy:metadata:userpref:dockedinstance'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist { global $DB; $contextlist = new \core_privacy\local\request\contextlist(); // Fetch the block instance IDs. $likehidden = $DB->sql_like('name', ':hidden', false, false); $likedocked = $DB->sql_like('name', ':docked', false, false); $sql = "userid = :userid AND ($likehidden OR $likedocked)"; $params = [ 'userid' => $userid, 'hidden' => 'block%hidden', 'docked' => 'docked_block_instance_%', ]; $prefs = $DB->get_fieldset_select('user_preferences', 'name', $sql, $params); $instanceids = array_unique(array_map(function($prefname) { if (preg_match('/^block(\d+)hidden$/', $prefname, $matches)) { return $matches[1]; } else if (preg_match('/^docked_block_instance_(\d+)$/', $prefname, $matches)) { return $matches[1]; } return 0; }, $prefs)); // Find the context of the instances. if (!empty($instanceids)) { list($insql, $inparams) = $DB->get_in_or_equal($instanceids, SQL_PARAMS_NAMED); $sql = " SELECT ctx.id FROM {context} ctx WHERE ctx.instanceid $insql AND ctx.contextlevel = :blocklevel"; $params = array_merge($inparams, ['blocklevel' => CONTEXT_BLOCK]); $contextlist->add_from_sql($sql, $params); } return $contextlist; } /** * Get the list of users who have data within a context. * * @param \core_privacy\local\request\userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) { global $DB; $context = $userlist->get_context(); if ($context->contextlevel != CONTEXT_BLOCK) { return; } $params = ['docked' => 'docked_block_instance_' . $context->instanceid, 'hidden' => 'block' . $context->instanceid . 'hidden']; $sql = "SELECT userid FROM {user_preferences} WHERE name = :hidden OR name = :docked"; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { global $DB; $userid = $contextlist->get_user()->id; // Extract the block instance IDs. $instanceids = array_reduce($contextlist->get_contexts(), function($carry, $context) { if ($context->contextlevel == CONTEXT_BLOCK) { $carry[] = $context->instanceid; } return $carry; }, []); if (empty($instanceids)) { return; } // Query the blocks and their preferences. list($insql, $inparams) = $DB->get_in_or_equal($instanceids, SQL_PARAMS_NAMED); $hiddenkey = $DB->sql_concat("'block'", 'bi.id', "'hidden'"); $dockedkey = $DB->sql_concat("'docked_block_instance_'", 'bi.id'); $sql = " SELECT bi.id, h.value AS prefhidden, d.value AS prefdocked FROM {block_instances} bi LEFT JOIN {user_preferences} h ON h.userid = :userid1 AND h.name = $hiddenkey LEFT JOIN {user_preferences} d ON d.userid = :userid2 AND d.name = $dockedkey WHERE bi.id $insql AND (h.id IS NOT NULL OR d.id IS NOT NULL)"; $params = array_merge($inparams, [ 'userid1' => $userid, 'userid2' => $userid, ]); // Export all the things. $dockedstr = get_string('privacy:request:blockisdocked', 'core_block'); $hiddenstr = get_string('privacy:request:blockishidden', 'core_block'); $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $context = context_block::instance($record->id); if ($record->prefdocked !== null) { writer::with_context($context)->export_user_preference( 'core_block', 'block_is_docked', transform::yesno($record->prefdocked), $dockedstr ); } if ($record->prefhidden !== null) { writer::with_context($context)->export_user_preference( 'core_block', 'block_is_hidden', transform::yesno($record->prefhidden), $hiddenstr ); } } $recordset->close(); } /** * Export all user preferences for the plugin. * * @param int $userid The userid of the user whose data is to be exported. */ public static function export_user_preferences(int $userid) { // Our preferences aren't site-wide so they are exported in export_user_data. } /** * Delete all data for all users in the specified context. * * @param context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(context $context) { global $DB; if ($context->contextlevel != CONTEXT_BLOCK) { return; } // Delete the user preferences. $instanceid = $context->instanceid; $DB->delete_records_list('user_preferences', 'name', [ "block{$instanceid}hidden", "docked_block_instance_{$instanceid}" ]); } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { global $DB; $userid = $contextlist->get_user()->id; $prefnames = array_reduce($contextlist->get_contexts(), function($carry, $context) { if ($context->contextlevel == CONTEXT_BLOCK) { $carry[] = "block{$context->instanceid}hidden"; $carry[] = "docked_block_instance_{$context->instanceid}"; } return $carry; }, []); if (empty($prefnames)) { return; } list($insql, $inparams) = $DB->get_in_or_equal($prefnames, SQL_PARAMS_NAMED); $sql = "userid = :userid AND name $insql"; $params = array_merge($inparams, ['userid' => $userid]); $DB->delete_records_select('user_preferences', $sql, $params); } /** * Delete multiple users within a single context. * * @param \core_privacy\local\request\approved_userlist $userlist The approved context and user information to delete * information for. */ public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) { global $DB; $context = $userlist->get_context(); if ($context->contextlevel != CONTEXT_BLOCK) { return; } list($insql, $params) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); $params['hidden'] = 'block' . $context->instanceid . 'hidden'; $params['docked'] = 'docked_block_instance_' . $context->instanceid; $DB->delete_records_select('user_preferences', "(name = :hidden OR name = :docked) AND userid $insql", $params); } } PK'9\R&classes/navigation/views/secondary.phpnu[. namespace core_block\navigation\views; /** * Class secondary * * @package core_block * @category navigation */ class secondary extends \core\navigation\views\secondary { /** * Blocks don't require secondary navs as they can be accessed from multiple places and in different contexts. */ public function initialise(): void { } } PK'9\c(xx$classes/output/block_move_target.phpnu[. namespace core_block\output; use moodle_url; /** * This class represents a target for where a block can go when it is being moved. * * This needs to be rendered as a form with the given hidden from fields, and * clicking anywhere in the form should submit it. The form action should be * $PAGE->url. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core_block * @category output */ class block_move_target { /** * Constructor. * * @param moodle_url $url */ public function __construct( /** @var moodle_url Move url */ public moodle_url $url, ) { } } // Alias this class to the old name. // This file will be autoloaded by the legacyclasses autoload system. // In future all uses of this class will be corrected and the legacy references will be removed. class_alias(block_move_target::class, \block_move_target::class); PK'9\z  !classes/output/block_contents.phpnu[. namespace core_block\output; /** * This class represents how a block appears on a page. * * During output, each block instance is asked to return a block_contents object, * those are then passed to the $OUTPUT->block function for display. * * contents should probably be generated using a moodle_block_..._renderer. * * Other block-like things that need to appear on the page, for example the * add new block UI, are also represented as block_contents objects. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core_block * @category output */ class block_contents { /** Used when the block cannot be collapsed **/ const NOT_HIDEABLE = 0; /** Used when the block can be collapsed but currently is not **/ const VISIBLE = 1; /** Used when the block has been collapsed **/ const HIDDEN = 2; /** * @var int Used to set $skipid. */ protected static $idcounter = 1; /** * @var int All the blocks (or things that look like blocks) printed on * a page are given a unique number that can be used to construct id="" attributes. * This is set automatically be the {@see prepare()} method. * Do not try to set it manually. */ public $skipid; /** * @var int If this is the contents of a real block, this should be set * to the block_instance.id. Otherwise this should be set to 0. */ public $blockinstanceid = 0; /** * @var int If this is a real block instance, and there is a corresponding * block_position.id for the block on this page, this should be set to that id. * Otherwise it should be 0. */ public $blockpositionid = 0; /** * @var array An array of attribute => value pairs that are put on the outer div of this * block. {@see $id} and {@see $classes} attributes should be set separately. */ public $attributes; /** * @var string The title of this block. If this came from user input, it should already * have had format_string() processing done on it. This will be output inside *

              tags. Please do not cause invalid XHTML. */ public $title = ''; /** * @var string The label to use when the block does not, or will not have a visible title. * You should never set this as well as title... it will just be ignored. */ public $arialabel = ''; /** * @var string HTML for the content */ public $content = ''; /** * @var array An alternative to $content, it you want a list of things with optional icons. */ public $footer = ''; /** * @var string Any small print that should appear under the block to explain * to the teacher about the block, for example 'This is a sticky block that was * added in the system context.' */ public $annotation = ''; /** * @var int One of the constants NOT_HIDEABLE, VISIBLE, HIDDEN. Whether * the user can toggle whether this block is visible. */ public $collapsible = self::NOT_HIDEABLE; /** * Set this to true if the block is dockable. * @var bool */ public $dockable = false; /** * @var array A (possibly empty) array of editing controls. Each element of * this array should be an array('url' => $url, 'icon' => $icon, 'caption' => $caption). * $icon is the icon name. Fed to $OUTPUT->image_url. */ public $controls = []; /** * Create new instance of block content. * * @param null|array $attributes */ public function __construct(?array $attributes = null) { $this->skipid = self::$idcounter; self::$idcounter += 1; if ($attributes) { // Standard block. $this->attributes = $attributes; } else { // Simple "fake" blocks used in some modules and "Add new block" block. $this->attributes = ['class' => 'block']; } } /** * Add html class to block * * @param string $class */ public function add_class($class) { $this->attributes['class'] .= ' ' . $class; } /** * Check if the block is a fake block. * * @return boolean */ public function is_fake() { return isset($this->attributes['data-block']) && $this->attributes['data-block'] == '_fake'; } } // Alias this class to the old name. // This file will be autoloaded by the legacyclasses autoload system. // In future all uses of this class will be corrected and the legacy references will be removed. class_alias(block_contents::class, \block_contents::class); PK'9\ j`))glossary_random/db/access.phpnu[. /** * Glossary random block caps. * * @package block_glossary_random * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/glossary_random:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/glossary_random:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\C 1glossary_random/lang/en/block_glossary_random.phpnu[. /** * Strings for component 'block_glossary_random', language 'en', branch 'MOODLE_20_STABLE' * * @package block_glossary_random * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['addentry'] = 'Add a new entry'; $string['askaddentry'] = 'When users can add entries to the glossary, show a link with this text'; $string['askinvisible'] = 'When users cannot edit or view the glossary, show this text (without link)'; $string['askviewglossary'] = 'When users can view the glossary but not add entries, show a link with this text'; $string['glossary_random:addinstance'] = 'Add a new random glossary entry block'; $string['glossary_random:myaddinstance'] = 'Add a new random glossary entry block to Dashboard'; $string['intro'] = 'Make sure you have at least one glossary with at least one entry added to this course. Then you can adjust the following settings'; $string['invisible'] = '(to be continued)'; $string['lastmodified'] = 'Last modified entry'; $string['nextalpha'] = 'Alphabetical order'; $string['nextone'] = 'Next entry'; $string['noentriesyet'] = 'There are no entries yet in the chosen glossary.'; $string['notyetconfigured'] = 'Please configure this block using the edit icon.'; $string['notyetglossary'] = 'You need to have at least one glossary to choose.'; $string['pluginname'] = 'Random glossary entry'; $string['random'] = 'Random entry'; $string['refresh'] = 'Days before a new entry is chosen'; $string['select_glossary'] = 'Take entries from this glossary'; $string['showconcept'] = 'Show concept (heading) for each entry'; $string['title'] = 'Title'; $string['type'] = 'How a new entry is chosen'; $string['viewglossary'] = 'View all entries'; $string['whichfooter'] = 'You can display links to actions of the glossary this block is associated with. The block will only display links to actions which are enabled for that glossary.'; $string['privacy:metadata'] = 'The Random glossary entry block only shows data stored in other locations.'; PK'9\ƜiiKglossary_random/backup/moodle2/restore_glossary_random_block_task.class.phpnu[. /** * @package block_glossary_random * @subpackage backup-moodle2 * @copyright 2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Specialised restore task for the glossary_random block * (using execute_after_tasks for recoding of glossaryid) * * TODO: Finish phpdocs */ class restore_glossary_random_block_task extends restore_block_task { protected function define_my_settings() { } protected function define_my_steps() { } public function get_fileareas() { return array(); // No associated fileareas } public function get_configdata_encoded_attributes() { return array(); // No special handling of configdata } /** * This function, executed after all the tasks in the plan * have been executed, will perform the recode of the * target glossary for the block. This must be done here * and not in normal execution steps because the glossary * may be restored after the block. */ public function after_restore() { global $DB; // Get the blockid $blockid = $this->get_blockid(); // Extract block configdata and update it to point to the new glossary if ($configdata = $DB->get_field('block_instances', 'configdata', array('id' => $blockid))) { $config = $this->decode_configdata($configdata); if (!empty($config->glossary)) { if ($glossarymap = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'glossary', $config->glossary)) { // Get glossary mapping and replace it in config $config->glossary = $glossarymap->newitemid; } else if ($this->is_samesite()) { // We are restoring on the same site, check if glossary can be used in the block in this course. $glossaryid = $DB->get_field_sql("SELECT id FROM {glossary} " . "WHERE id = ? AND (course = ? OR globalglossary = 1)", [$config->glossary, $this->get_courseid()]); if (!$glossaryid) { unset($config->glossary); } } else { // The block refers to a glossary not present in the backup file. unset($config->glossary); } // Unset config variables that are no longer used. unset($config->globalglossary); unset($config->courseid); // Save updated config. $configdata = base64_encode(serialize($config)); $DB->set_field('block_instances', 'configdata', $configdata, array('id' => $blockid)); } } } public static function define_decode_contents() { return array(); } public static function define_decode_rules() { return array(); } } PK'9\rgglossary_random/edit_form.phpnu[. /** * Form for editing HTML block instances. * * @package block_glossary_random * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Form for editing Random glossary entry block instances. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_glossary_random_edit_form extends block_edit_form { protected function specific_definition($mform) { global $DB; // Fields for editing HTML block title and contents. $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $mform->addElement('text', 'config_title', get_string('title', 'block_glossary_random')); $mform->setDefault('config_title', get_string('pluginname','block_glossary_random')); $mform->setType('config_title', PARAM_TEXT); // Select glossaries to put in dropdown box ... $glossaries = $DB->get_records_select_menu('glossary', 'course = ? OR globalglossary = ?', [$this->get_course_id(), 1], 'name', 'id,name'); foreach($glossaries as $key => $value) { $glossaries[$key] = strip_tags(format_string($value, true)); } $mform->addElement('select', 'config_glossary', get_string('select_glossary', 'block_glossary_random'), $glossaries); $mform->addElement('text', 'config_refresh', get_string('refresh', 'block_glossary_random'), array('size' => 5)); $mform->setDefault('config_refresh', 0); $mform->setType('config_refresh', PARAM_INT); // and select quotetypes to put in dropdown box $types = array( 0 => get_string('random','block_glossary_random'), 1 => get_string('lastmodified','block_glossary_random'), 2 => get_string('nextone','block_glossary_random'), 3 => get_string('nextalpha','block_glossary_random') ); $mform->addElement('select', 'config_type', get_string('type', 'block_glossary_random'), $types); $mform->addElement('selectyesno', 'config_showconcept', get_string('showconcept', 'block_glossary_random')); $mform->setDefault('config_showconcept', 1); $mform->addElement('static', 'footerdescription', '', get_string('whichfooter', 'block_glossary_random')); $mform->addElement('text', 'config_addentry', get_string('askaddentry', 'block_glossary_random')); $mform->setDefault('config_addentry', get_string('addentry', 'block_glossary_random')); $mform->setType('config_addentry', PARAM_NOTAGS); $mform->addElement('text', 'config_viewglossary', get_string('askviewglossary', 'block_glossary_random')); $mform->setDefault('config_viewglossary', get_string('viewglossary', 'block_glossary_random')); $mform->setType('config_viewglossary', PARAM_NOTAGS); $mform->addElement('text', 'config_invisible', get_string('askinvisible', 'block_glossary_random')); $mform->setDefault('config_invisible', get_string('invisible', 'block_glossary_random')); $mform->setType('config_invisible', PARAM_NOTAGS); } /** * Returns id of the course where this block is located (or siteid for the dashboard or non-course page) * * @return int */ protected function get_course_id(): int { if ($this->block->instance->id) { return $this->block->course->id; } else if ($parentcontext = $this->block->context->get_course_context(false)) { return $parentcontext->instanceid; } else { return get_site()->id; } } /** * Display the configuration form when block is being added to the page * * @return bool */ public static function display_form_when_adding(): bool { return true; } } PK'9\hglossary_random/version.phpnu[. /** * Version details * * @package block_glossary_random * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_glossary_random'; // Full name of the plugin (used for diagnostics) $plugin->dependencies = ['mod_glossary' => 2025040800]; PK'9\000)glossary_random/block_glossary_random.phpnu[. /** * Glossary Random block. * * @package block_glossary_random * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define('BGR_RANDOMLY', '0'); define('BGR_LASTMODIFIED', '1'); define('BGR_NEXTONE', '2'); define('BGR_NEXTALPHA', '3'); class block_glossary_random extends block_base { /** * @var cm_info|stdClass has properties 'id' (course module id) and 'uservisible' * (whether the glossary is visible to the current user) */ protected $glossarycm = null; /** @var stdClass course data. */ public $course; function init() { $this->title = get_string('pluginname','block_glossary_random'); } function specialization() { global $CFG, $DB; require_once($CFG->libdir . '/filelib.php'); $this->course = $this->page->course; // load userdefined title and make sure it's never empty if (empty($this->config->title)) { $this->title = get_string('pluginname','block_glossary_random'); } else { $this->title = format_string($this->config->title, true, ['context' => $this->context]); } if (empty($this->config->glossary)) { return false; } if (!isset($this->config->nexttime)) { $this->config->nexttime = 0; } //check if it's time to put a new entry in cache if (time() > $this->config->nexttime) { if (!($cm = $this->get_glossary_cm()) || !$cm->uservisible) { // Skip generating of the cache if we can't display anything to the current user. return false; } // place glossary concept and definition in $pref->cache if (!$numberofentries = $DB->count_records('glossary_entries', array('glossaryid'=>$this->config->glossary, 'approved'=>1))) { $this->config->cache = get_string('noentriesyet','block_glossary_random'); $this->instance_config_commit(); } $glossaryctx = context_module::instance($cm->id); $entries = $DB->get_records_sql('SELECT id, concept, definition, definitionformat, definitiontrust FROM {glossary_entries} WHERE glossaryid = ? AND approved = 1 ORDER BY timemodified ASC', [$this->config->glossary]); // Reset entries keys, so subsequent selection by index works correctly. $entries = array_values($entries); if (empty($entries)) { $text = get_string('noentriesyet', 'block_glossary_random'); } else { // Now picking out the correct entry from the array. switch ($this->config->type) { case BGR_RANDOMLY: $i = ($numberofentries > 1) ? rand(1, $numberofentries) : 1; if (count($entries) == 1) { $entry = reset($entries); } else { $entry = $entries[$i - 1]; } break; case BGR_NEXTONE: // The array is already sorted by last modified. if (isset($this->config->previous)) { $i = $this->config->previous + 1; } else { $i = 1; } if ($i > $numberofentries) { // Loop back to beginning. $i = 1; } if (count($entries) == 1) { $entry = reset($entries); } else { $entry = $entries[$i - 1]; } break; case BGR_NEXTALPHA: // Now sort the array in regard to the current language. usort($entries, function($a, $b) { return format_string($a->concept) <=> format_string($b->concept); }); if (isset($this->config->previous)) { $i = $this->config->previous + 1; } else { $i = 1; } if ($i > $numberofentries) { // Loop back to beginning. $i = 1; } if (count($entries) == 1) { $entry = $entries; } else { $entry = $entries[$i - 1]; } break; default: // BGR_LASTMODIFIED // The array is already sorted by last modified. $i = $numberofentries; if (count($entries) == 1) { $entry = reset($entries); } else { $entry = array_pop($entries); } break; } if (empty($this->config->showconcept) || (!isset($entry->concept))) { $text = ''; } else { $text = "

              " . format_string($entry->concept, true, ["context" => $glossaryctx]) . "

              "; } $options = new stdClass(); if (isset($entry->definitiontrust)) { $options->trusted = $entry->definitiontrust; } $options->overflowdiv = true; if (isset($entry->definitiontrust) && isset($entry->id) && isset($entry->definition)) { $entry->definition = file_rewrite_pluginfile_urls($entry->definition, 'pluginfile.php', $glossaryctx->id, 'mod_glossary', 'entry', $entry->id); $text .= format_text($entry->definition, $entry->definitionformat, $options); } $this->config->nexttime = usergetmidnight(time()) + DAYSECS * $this->config->refresh; $this->config->previous = $i; } // store the text $this->config->cache = $text; $this->instance_config_commit(); } } /** * Replace the instance's configuration data with those currently in $this->config; */ function instance_config_commit($nolongerused = false) { // Unset config variables that are no longer used. unset($this->config->globalglossary); unset($this->config->courseid); parent::instance_config_commit($nolongerused); } /** * Checks if glossary is available - it should be either located in the same course or be global * * @return null|cm_info|stdClass object with properties 'id' (course module id) and 'uservisible' */ protected function get_glossary_cm() { global $DB; if (empty($this->config->glossary)) { // No glossary is configured. return null; } if (!empty($this->glossarycm)) { return $this->glossarycm; } if (!empty($this->page->course->id)) { // First check if glossary belongs to the current course (we don't need to make any DB queries to find it). $modinfo = get_fast_modinfo($this->page->course); if (isset($modinfo->instances['glossary'][$this->config->glossary])) { $this->glossarycm = $modinfo->instances['glossary'][$this->config->glossary]; if ($this->glossarycm->uservisible) { // The glossary is in the same course and is already visible to the current user, // no need to check if it is global, save on DB query. return $this->glossarycm; } } } // Find course module id for the given glossary, only if it is global. $cm = $DB->get_record_sql("SELECT cm.id, cm.visible AS uservisible FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module JOIN {glossary} g ON g.id = cm.instance WHERE g.id = :instance AND md.name = :modulename AND g.globalglossary = 1", ['instance' => $this->config->glossary, 'modulename' => 'glossary']); if ($cm) { // This is a global glossary, create an object with properties 'id' and 'uservisible'. We don't need any // other information so why bother retrieving it. Full access check is skipped for global glossaries for // performance reasons. $this->glossarycm = $cm; } else if (empty($this->glossarycm)) { // Glossary does not exist. Remove it in the config so we don't repeat this check again later. $this->config->glossary = 0; $this->instance_config_commit(); } return $this->glossarycm; } function instance_allow_multiple() { // Are you going to allow multiple instances of each block? // If yes, then it is assumed that the block WILL USE per-instance configuration return true; } function get_content() { if ($this->content !== null) { return $this->content; } $this->content = (object)['text' => '', 'footer' => '']; if (!$cm = $this->get_glossary_cm()) { if ($this->user_can_edit()) { $this->content->text = get_string('notyetconfigured', 'block_glossary_random'); } return $this->content; } if (empty($this->config->cache)) { $this->config->cache = ''; } if ($cm->uservisible) { // Show glossary if visible and place links in footer. $this->content->text = $this->config->cache; if (has_capability('mod/glossary:write', context_module::instance($cm->id))) { $this->content->footer = html_writer::link(new moodle_url('/mod/glossary/edit.php', ['cmid' => $cm->id]), format_string($this->config->addentry)) . '
              '; } $this->content->footer .= html_writer::link(new moodle_url('/mod/glossary/view.php', ['id' => $cm->id]), format_string($this->config->viewglossary)); } else { // Otherwise just place some text, no link. $this->content->footer = format_string($this->config->invisible); } return $this->content; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = !empty($this->config) ? $this->config : new stdClass(); return (object) [ 'instance' => $configs, 'plugin' => new stdClass(), ]; } /** * This block shouldn't be added to a page if the glossary module is disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { $pluginclass = \core_plugin_manager::resolve_plugininfo_class('mod'); return $pluginclass::get_enabled_plugin('glossary'); } } PK'9\ ,glossary_random/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_glossary_random. * * @package block_glossary_random * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_glossary_random\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_glossary_random implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\bic c .glossary_random/tests/glossary_random_test.phpnu[. namespace block_glossary_random; use advanced_testcase; use block_glossary_random; use context_course; /** * PHPUnit block_glossary_random tests * * @package block_glossary_random * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_glossary_random */ final class glossary_random_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_glossary_random.php'); parent::setUpBeforeClass(); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_glossary_random(); $pluginclass = \core_plugin_manager::resolve_plugininfo_class('mod'); // If glossary module is enabled, the method should return true. $pluginclass::enable_plugin('glossary', 1); $this->assertTrue($block->can_block_be_added($page)); // However, if the glossary module is disabled, the method should return false. $pluginclass::enable_plugin('glossary', 0); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\*ϑ3glossary_random/tests/behat/glossary_random.featurenu[@block @block_glossary_random Feature: Random glossary entry block is used in a course In order to show the entries from glossary As a teacher I can add the random glossary entry to a course page Background: Given the following "courses" exist: | fullname | shortname | | Course 1 | C1 | And the following "users" exist: | username | firstname | lastname | email | | student1 | Sam1 | Student1 | student1@example.com | | teacher1 | Terry1 | Teacher1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | teacher1 | C1 | editingteacher | Scenario: Student can not see the block if it is not configured When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Random glossary entry" block Then I should see "Please configure this block using the edit icon" in the "block_glossary_random" "block" And I log out And I log in as "student1" And I am on "Course 1" course homepage And "block_glossary_random" "block" should not exist And I log out Scenario: View random (last) entry in the glossary with auto approval Given the following "activities" exist: | activity | name | intro | course | idnumber | defaultapproval | | glossary | GlossaryAuto | Test glossary description | C1 | glossary1 | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Random glossary entry" block And I configure the "block_glossary_random" block And I set the following fields to these values: | Title | AutoGlossaryblock | | Take entries from this glossary | GlossaryAuto | | How a new entry is chosen | Last modified entry | And I press "Save changes" And I log out When I log in as "student1" And I am on "Course 1" course homepage Then I should see "There are no entries yet in the chosen glossary" in the "AutoGlossaryblock" "block" And I click on "Add a new entry" "link" in the "AutoGlossaryblock" "block" And I set the following fields to these values: | Concept | Concept1 | | Definition | Definition1 | And I press "Save changes" And I am on "Course 1" course homepage And I should see "Concept1" in the "AutoGlossaryblock" "block" And I should see "Definition1" in the "AutoGlossaryblock" "block" And I should not see "There are no entries yet in the chosen glossary" in the "AutoGlossaryblock" "block" And I click on "Add a new entry" "link" in the "AutoGlossaryblock" "block" And I set the following fields to these values: | Concept | Concept2 | | Definition | Definition2 | And I press "Save changes" And I am on "Course 1" course homepage # Only the last entry appears in the block And I should not see "Concept1" in the "AutoGlossaryblock" "block" And I should not see "Definition1" in the "AutoGlossaryblock" "block" And I should see "Concept2" in the "AutoGlossaryblock" "block" And I should see "Definition2" in the "AutoGlossaryblock" "block" And I click on "View all entries" "link" in the "AutoGlossaryblock" "block" And I should see "GlossaryAuto" in the "#page-navbar" "css_element" And I should see "Concept1" in the "#page-content" "css_element" And I should see "Concept2" in the "#page-content" "css_element" And I log out Scenario: View random (last) entry in the glossary with manual approval Given the following "activities" exist: | activity | name | intro | course | idnumber | defaultapproval | | glossary | GlossaryManual | Test glossary description | C1 | glossary2 | 0 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Random glossary entry" block And I configure the "block_glossary_random" block And I set the following fields to these values: | Title | ManualGlossaryblock | | Take entries from this glossary | GlossaryManual | | How a new entry is chosen | Last modified entry | And I press "Save changes" And I log out When I log in as "student1" And I am on "Course 1" course homepage Then I should see "There are no entries yet in the chosen glossary" in the "ManualGlossaryblock" "block" And I click on "Add a new entry" "link" in the "ManualGlossaryblock" "block" And I set the following fields to these values: | Concept | Concept1 | | Definition | Definition1 | And I press "Save changes" And I am on "Course 1" course homepage And I should see "There are no entries yet in the chosen glossary" in the "ManualGlossaryblock" "block" And I log out And I log in as "teacher1" And I am on "Course 1" course homepage And I should see "There are no entries yet in the chosen glossary" in the "ManualGlossaryblock" "block" And I follow "GlossaryManual" And I follow "Pending approval" And I follow "Approve" And I click on "Course 1" "link" in the "#page-navbar" "css_element" And I should see "Concept1" in the "ManualGlossaryblock" "block" And I should see "Definition1" in the "ManualGlossaryblock" "block" And I log out PK'9\xx:glossary_random/tests/behat/glossary_random_global.featurenu[@block @block_glossary_random Feature: Random glossary entry block linking to global glossary In order to show the entries from glossary As a teacher I can add the random glossary entry to a course page Background: Given the following "courses" exist: | fullname | shortname | | Course 1 | C1 | | Course 2 | C2 | And the following "activity" exists: | activity | glossary | | name | Tips and Tricks | | intro | Frontpage glossary description | | course | C2 | | idnumber | glossary0 | | globalglossary | 1 | | defaultapproval | 1 | And the following "users" exist: | username | firstname | lastname | email | | student1 | Sam1 | Student1 | student1@example.com | | teacher1 | Terry1 | Teacher1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | teacher1 | C1 | editingteacher | Scenario: View random (last) entry in the global glossary When I log in as "admin" And I am on "Course 2" course homepage And I follow "Tips and Tricks" And I press "Add entry" And I set the following fields to these values: | Concept | Never come late | | Definition | Come in time for your classes | And I press "Save changes" And I log out # As a teacher add a block to the course page linking to the global glossary. And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Random glossary entry" block And I configure the "block_glossary_random" block And I set the following fields to these values: | Title | Tip of the day | | Take entries from this glossary | Tips and Tricks | | How a new entry is chosen | Last modified entry | And I press "Save changes" Then I should see "Never come late" in the "Tip of the day" "block" And I should not see "Add a new entry" in the "Tip of the day" "block" And I should see "View all entries" in the "Tip of the day" "block" And I log out # Student who can't see the module is still able to view entries in this block (because the glossary was marked as global) And I log in as "student1" And I am on "Course 1" course homepage And I should see "Never come late" in the "Tip of the day" "block" And I should not see "Add a new entry" in the "Tip of the day" "block" And I should see "View all entries" in the "Tip of the day" "block" And I log out Scenario: Removing the global glossary that is used in random glossary block And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Random glossary entry" block And I configure the "block_glossary_random" block And I set the following fields to these values: | Title | Tip of the day | | Take entries from this glossary | Tips and Tricks | | How a new entry is chosen | Last modified entry | And I press "Save changes" And I log out And I log in as "admin" And I am on "Course 2" course homepage And I follow "Tips and Tricks" And I follow "Settings" And I set the field "globalglossary" to "0" And I press "Save and return to course" And I am on "Course 1" course homepage Then I should see "Please configure this block using the edit icon." in the "Tip of the day" "block" And I log out And I log in as "student1" And I am on "Course 1" course homepage And "Tip of the day" "block" should not exist And I log out PK'9\Eglossary_random/tests/behat/glossary_random_addblock_disabled.featurenu[@block @block_glossary_random @javascript Feature: Add the glossary random block when main feature is disabled In order to add the glossary random block to my course As a teacher It should be added to courses only if the glossary module is enabled. Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | glossary_random | Course | C1 | course-view-* | site-post | Scenario: The glossary random block is displayed even when glossary module is disabled When I log in as "admin" And I navigate to "Plugins > Activity modules > Manage activities" in site administration And I toggle the "Disable Glossary" admin switch "off" And I am on "Course 1" course homepage with editing mode on Then "Random glossary entry" "block" should exist Scenario: The glossary random block can be removed even when glossary module is disabled When I log in as "admin" And I am on "Course 1" course homepage with editing mode on And I open the "Random glossary entry" blocks action menu And I click on "Delete Random glossary entry block" "link" in the "Random glossary entry" "block" And "Delete block?" "dialogue" should exist And I click on "Cancel" "button" in the "Delete block?" "dialogue" And "Random glossary entry" "block" should exist When I navigate to "Plugins > Activity modules > Manage activities" in site administration And I toggle the "Disable Glossary" admin switch "off" And I am on "Course 1" course homepage with editing mode on And I open the "Random glossary entry" blocks action menu And I click on "Delete Random glossary entry block" "link" in the "Random glossary entry" "block" And I click on "Delete" "button" in the "Delete block?" "dialogue" Then "Random glossary entry" "block" should not exist PK'9\Lnn=glossary_random/tests/behat/glossary_random_frontpage.featurenu[@block @block_glossary_random Feature: Random glossary entry block can be added to the frontpage In order to show the entries from glossary on the frontpage As a teacher I can add the random glossary entry to the frontpage Scenario: Admin can add random glossary block to the frontpage Given the following "activities" exist: | activity | name | intro | course | idnumber | | glossary | Tips and Tricks | Frontpage glossary description | Acceptance test site | glossary0 | And I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Random glossary entry" block And I configure the "block_glossary_random" block And I set the following fields to these values: | Title | Tip of the day | | Take entries from this glossary | Tips and Tricks | And I press "Save changes" And I click on "Add a new entry" "link" in the "Tip of the day" "block" And I set the following fields to these values: | Concept | Never come late | | Definition | Come in time for your classes | And I press "Save changes" When I log out Then I should see "Never come late" in the "Tip of the day" "block" And I should see "Come in time for your classes" in the "Tip of the day" "block" PK'9\]49 9 <glossary_random/tests/behat/glossary_random_addblock.featurenu[@block @block_glossary_random @javascript @addablocklink Feature: Add the glossary random block when main feature is enabled In order to add the glossary random block to my course As a teacher It should be added to courses only if the glossary module is enabled. Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | teacher1 | C1 | editingteacher | And I am on the "C1" "course" page logged in as "admin" Scenario: The glossary random block can be added when glossary module is enabled Given I turn editing mode on When I click on "Add a block" "link" Then I should see "Random glossary entry" Scenario: The glossary random block cannot be added when glossary module is disabled Given I navigate to "Plugins > Activity modules > Manage activities" in site administration And I toggle the "Disable Glossary" admin switch "off" And I am on "Course 1" course homepage with editing mode on When I click on "Add a block" "link" Then I should not see "Random glossary entry" Scenario: View alphabetical order multilang entries in the glossary block Given the following "activities" exist: | activity | name | intro | course | idnumber | defaultapproval | | glossary | Animals | An animal glossary | C1 | glossary3 | 1 | And the following "mod_glossary > entries" exist: | glossary | user | concept | definition | | Animals | student1 | Aardvark | AardvarkErdferkel | | Animals | student1 | Kangaroo | KangarooKänguru | | Animals | student1 | Zebra | ZebraZebra | And the "multilang" filter is "on" And the "multilang" filter applies to "content and headings" And I log out And I log in as "teacher1" And I am on "C1" course homepage with editing mode on And I add the "Random glossary entry..." block And I set the following fields to these values: | Title | ManualGlossaryblock | | Take entries from this glossary | Animals | | Days before a new entry is chosen | 0 | | How a new entry is chosen | Alphabetical order | And I press "Save changes" And I should see "Aardvark" in the "ManualGlossaryblock" "block" And I should not see "AardvarkErdferkel" in the "ManualGlossaryblock" "block" And I reload the page And I should see "Kangaroo" in the "ManualGlossaryblock" "block" And I reload the page Then I should see "Zebra" in the "ManualGlossaryblock" "block" And I log out PK'9\Uαi upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This files describes API changes in /blocks/* - activity modules, information provided here is intended especially for developers. === 4.2 === * Block configuration form is now displayed in a modal. Class 'block_edit_form' now extends '\core_form\dynamic_form' and properties $block and $page are read-only. Normally no changes should be necessary to keep the old behavior of configuration form. * Block plugins can specify that they want to display configuration form when block is added to the page by overriding static method block_edit_form::display_form_when_adding(). * Web service 'core_block_fetch_addable_blocks' accepts new parameter 'pagehash' * JS module 'core/addblockmodal' is deprecated, instead there is new module 'core_block/add_modal' with similar functionality but different arguments. * Functions in the JS modules 'moodle-core-blockdraganddrop' have changed their parameters. File lib/ajax/blocks.php takes different arguments. No backward-compatibility is provided since these files are not part of the blocks API and not intended to be used by plugins. === 4.0 === * Block block_quiz_results has been completely removed from core. The Quiz results block is hidden by default since Moodle 2.9. It is recommended to use the Activity results block instead, which works with any type of activity (not just quizzes). * External function core_block::get_dashboard_blocks has a new parameter to indicate if you want to receive the block on the my/courses page. * The `core_block_fetch_addable_blocks` external method accepts an optional `subpage` parameter, in order to correctly calculate available blocks for pages that use this property (e.g. the user dashboard) * A new method, can_block_be_added(), has been added to let blocks override it when they want to include some extra checks to decide whether the block can be added to a page or not. === 3.8 === * Block block_community is no longer a part of core. * Block block_participants is no longer a part of core. * Block plugins should overwrite get_config_for_external function to return the blocks settings viewable by the current user. If the block plugin does not have any setting that could be considerated private (like a private/access key/token), is ok to return all the settings via the get_config_for_external function. === 3.7 === * The block:addinstance capability is no longer required if the block can only be added to a dashboard. === 3.6 === * The timeline view from block_myoverview has been split out into block_timeline. * External function core_blocks::get_course_blocks now returns the block visible status and weight for ordering. * New method added block_base::get_content_for_external(). It will return all the block contents rendered for external functions. If your block is returning formatted content or provide files for download, you should override this method to use the external_format_text, external_format_string functions for formatting or external_util::get_area_files for files. See block_html as example. * External functions core_block::get_course_blocks and core_block::get_dashboard_blocks have a new parameter to indicate if you want to receive the block contents. === 3.4 === * The block_instances table now contains fields timecreated and timemodified. If third-party code creates or updates these rows (without using the standard API), it should be modified to set these fields as appropriate. * Blocks can now be included in Moodle global search, with some limitations (at present, the search works only for blocks located directly on course pages or site home page). See the HTML block for an example. * Block block_messages is no longer a part of core. === 3.3 === * block_manager::get_required_by_theme_block_types() is no longer static. * The plugin block_course_overview has been removed from core and is being replaced by block_myoverview. During the upgrade process the block_course_overview block will be uninstalled and all its settings will be deleted. If you wish to keep the block_course_overview block and its settings, download it from moodle.org and put it back in the blocks/ directory BEFORE UPGRADING. === 3.1 === * The collapsed class was removed from the navigation block to make it compatible with aria. * New aria attributes were added on the navigation block [aria-expanded="false"]. * The tree JS handling were moved from YUI to AMD module (Jquery). === 2.9 === * The obsolete method preferred_width() was removed (it was not doing anything) * Deprecated block_base::config_save as is not called anywhere and should not be used. * Added instance_copy() function to the block_base class. This function allows for block specific data to be copied when a block is copied. === 2.8 === * The instance_config_print() function was removed. It was deprecated in Moodle 2.0, but without debugging notices. Since it was no longer a part of the code path, debugging notices would not have been displayed. * Deprecated functions were removed from the block_base class: ** _print_block() ** _print_shadow() ** _title_html() ** _add_edit_controls() ** config_print() === 2.6 === * Deprecated /admin/block.php was removed, make sure blocks are using settings.php instead. === 2.4 === Created new capability 'blocks/xxx:myaddinstance' that determines whether a user can add a specific block to their My Home page. This capability was only defined for blocks where the applicable_formats function does not include "'my' => false" in the returned array, allowing it be added to the My Home page. === 2.3 === required changes in code: * block_xxx_pluginfile() is now given the 7th parameter (hopefully the last one) that contains additional options for the file serving. The array should be re-passed to send_stored_file(). === 2.0 === required changes in code: * use new DML syntax everywhere * use new DDL syntax in db/upgrade.php * replace defaults.php by settings.php and db/install.php * replace STATEMENTS section in db/install.xml by db/install.php * move post instalation code from install() method into db/install.php * completely rewrite file handling * rewrite backup/restore * theme changes: move plugin styles into blocks/xxx/styles.css and use new css markers for images, move all images into new blocks/xxx/pix/ directory and use new outputlib api old global $THEME is fully replaced by $OUTPUT * remove '_utf8' from language pack names, use new {$a} syntax in language packs * use 'pluginname' lang pack identifier instead of 'blockname' * move cron and version number into standard version.php * removed support for old config_global.html, use settings.php PK'9\'/myprofile/styles.cssnu[.block_myprofile img.profilepicture { height: 50px; width: 50px; } .block_myprofile .myprofileitem.fullname { font-size: 1.5em; font-weight: bold; margin-bottom: 0.5rem; } .block_myprofile .myprofileitem.edit { text-align: right; } .block_myprofile .content { display: flex; } .block_myprofile .myprofileitem.picture img { width: 50px; height: 50px; margin-right: 1rem; } .block_myprofile .myprofileitem span { font-weight: bold; } PK'9\ amyprofile/db/access.phpnu[. /** * My profile block caps. * * @package block_myprofile * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/myprofile:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/myprofile:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\a_jj%myprofile/lang/en/block_myprofile.phpnu[. /** * Strings for component 'block_myprofile', language 'en', branch 'MOODLE_20_STABLE' * * @package block_myprofile * @copyright 2010 Remote-Learner.net * @author Olav Jordan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['contentsettings'] = 'Display settings for content region'; $string['display_picture'] = 'Display picture'; $string['display_country'] = 'Display country'; $string['display_city'] = 'Display city'; $string['display_email'] = 'Display email'; $string['display_phone1'] = 'Display phone'; $string['display_phone2'] = 'Display mobile phone'; $string['display_idnumber'] = 'Display ID number'; $string['display_institution'] = 'Display institution'; $string['display_address'] = 'Display address'; $string['display_firstaccess'] = 'Display first access'; $string['display_lastaccess'] = 'Display last access'; $string['display_currentlogin'] = 'Display current login'; $string['display_lastip'] = 'Display last IP'; $string['display_lastlogin'] = 'Display last login'; $string['myprofile:addinstance'] = 'Add a new logged in user block'; $string['myprofile:myaddinstance'] = 'Add a new logged in user block to Dashboard'; $string['myprofile_settings'] = 'Visible user information'; $string['pluginname'] = 'Logged in user'; $string['privacy:metadata'] = 'The Logged in user block only shows information about the logged in user and does not store data itself.'; PK'9\;w!!myprofile/edit_form.phpnu[. defined('MOODLE_INTERNAL') || die(); /** * Form for editing profile block settings * * @package block_myprofile * @copyright 2010 Remote-Learner.net * @author Olav Jordan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_myprofile_edit_form extends block_edit_form { protected function specific_definition($mform) { global $CFG; $mform->addElement('header', 'configheader', get_string('myprofile_settings', 'block_myprofile')); $mform->addElement('selectyesno', 'config_display_picture', get_string('display_picture', 'block_myprofile')); if (isset($this->block->config->display_picture)) { $mform->setDefault('config_display_picture', $this->block->config->display_picture); } else { $mform->setDefault('config_display_picture', '1'); } $mform->addElement('selectyesno', 'config_display_country', get_string('display_country', 'block_myprofile')); if (isset($this->block->config->display_country)) { $mform->setDefault('config_display_country', $this->block->config->display_country); } else { $mform->setDefault('config_display_country', '1'); } $mform->addElement('selectyesno', 'config_display_city', get_string('display_city', 'block_myprofile')); if (isset($this->block->config->display_city)) { $mform->setDefault('config_display_city', $this->block->config->display_city); } else { $mform->setDefault('config_display_city', '1'); } $mform->addElement('selectyesno', 'config_display_email', get_string('display_email', 'block_myprofile')); if (isset($this->block->config->display_email)) { $mform->setDefault('config_display_email', $this->block->config->display_email); } else { $mform->setDefault('config_display_email', '1'); } $mform->addElement('selectyesno', 'config_display_phone1', get_string('display_phone1', 'block_myprofile')); if (isset($this->block->config->display_phone1)) { $mform->setDefault('config_display_phone1', $this->block->config->display_phone1); } else { $mform->setDefault('config_display_phone1', '0'); } $mform->addElement('selectyesno', 'config_display_phone2', get_string('display_phone2', 'block_myprofile')); if (isset($this->block->config->display_phone2)) { $mform->setDefault('config_display_phone2', $this->block->config->display_phone2); } else { $mform->setDefault('config_display_phone2', '0'); } $mform->addElement('selectyesno', 'config_display_institution', get_string('display_institution', 'block_myprofile')); if (isset($this->block->config->display_institution)) { $mform->setDefault('config_display_institution', $this->block->config->display_institution); } else { $mform->setDefault('config_display_institution', '0'); } $mform->addElement('selectyesno', 'config_display_address', get_string('display_address', 'block_myprofile')); if (isset($this->block->config->display_address)) { $mform->setDefault('config_display_address', $this->block->config->display_address); } else { $mform->setDefault('config_display_address', '0'); } $mform->addElement('selectyesno', 'config_display_idnumber', get_string('display_idnumber', 'block_myprofile')); if (isset($this->block->config->display_idnumber)) { $mform->setDefault('config_display_idnumber', $this->block->config->display_idnumber); } else { $mform->setDefault('config_display_idnumber', '0'); } $mform->addElement('selectyesno', 'config_display_firstaccess', get_string('display_firstaccess', 'block_myprofile')); if (isset($this->block->config->display_firstaccess)) { $mform->setDefault('config_display_firstaccess', $this->block->config->display_firstaccess); } else { $mform->setDefault('config_display_firstaccess', '0'); } $mform->addElement('selectyesno', 'config_display_lastaccess', get_string('display_lastaccess', 'block_myprofile')); if (isset($this->block->config->display_lastaccess)) { $mform->setDefault('config_display_lastaccess', $this->block->config->display_lastaccess); } else { $mform->setDefault('config_display_lastaccess', '0'); } $mform->addElement('selectyesno', 'config_display_currentlogin', get_string('display_currentlogin', 'block_myprofile')); if (isset($this->block->config->display_currentlogin)) { $mform->setDefault('config_display_currentlogin', $this->block->config->display_currentlogin); } else { $mform->setDefault('config_display_currentlogin', '0'); } $mform->addElement('selectyesno', 'config_display_lastip', get_string('display_lastip', 'block_myprofile')); if (isset($this->block->config->display_lastip)) { $mform->setDefault('config_display_lastip', $this->block->config->display_lastip); } else { $mform->setDefault('config_display_lastip', '0'); } $mform->addElement('selectyesno', 'config_display_lastlogin', get_string('display_lastlogin', 'block_myprofile')); if (isset($this->block->config->display_lastlogin)) { $mform->setDefault('config_display_lastlogin', $this->block->config->display_lastlogin); } else { $mform->setDefault('config_display_lastlogin', '0'); } } }PK'9\*Rmyprofile/version.phpnu[. /** * Current user info block. * * @package block_myprofile * @copyright 2010 Remote-Learner.net * @author Olav Jordan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_myprofile'; // Full name of the plugin (used for diagnostics) PK'9\ ))myprofile/block_myprofile.phpnu[. /** * Block displaying information about current logged-in user. * * This block can be used as anti cheating measure, you * can easily check the logged-in user matches the person * operating the computer. * * @package block_myprofile * @copyright 2010 Remote-Learner.net * @author Olav Jordan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Displays the current user's profile information. * * @copyright 2010 Remote-Learner.net * @author Olav Jordan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_myprofile extends block_base { /** * block initializations */ public function init() { $this->title = get_string('pluginname', 'block_myprofile'); } /** * block contents * * @return object */ public function get_content() { if ($this->content !== NULL) { return $this->content; } if (!isloggedin() or isguestuser()) { // Only real users can access myprofile block. return; } $renderable = new \block_myprofile\output\myprofile($this->config); $renderer = $this->page->get_renderer('block_myprofile'); $this->content = new stdClass(); $this->content->text = $renderer->render($renderable); $this->content->footer = ''; return $this->content; } /** * allow the block to have a configuration page * * @return boolean */ public function has_config() { return false; } /** * allow more than one instance of the block on a page * * @return boolean */ public function instance_allow_multiple() { //allow more than one instance on a page return false; } /** * allow instances to have their own configuration * * @return boolean */ function instance_allow_config() { //allow instances to have their own configuration return false; } /** * instance specialisations (must have instance allow config true) * */ public function specialization() { } /** * locations where block can be displayed * * @return array */ public function applicable_formats() { return array('all'=>true); } /** * post install configurations * */ public function after_install() { } /** * post delete configurations * */ public function before_delete() { } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = !empty($this->config) ? $this->config : new stdClass(); return (object) [ 'instance' => $configs, 'plugin' => new stdClass(), ]; } } PK'9\,r]&myprofile/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_myprofile. * * @package block_myprofile * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_myprofile\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_myprofile implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\cq!22%myprofile/classes/output/renderer.phpnu[. /** * myprofile block rendrer * * @package block_myprofile * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_myprofile\output; defined('MOODLE_INTERNAL') || die; use plugin_renderer_base; /** * myprofile block renderer * * @package block_myprofile * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends plugin_renderer_base { /** * Return the main content for the block myprofile. * * @param myprofile $myprofile The myprofile renderable * @return string HTML string */ public function render_myprofile(myprofile $myprofile) { return $this->render_from_template('block_myprofile/myprofile', $myprofile->export_for_template($this)); } } PK'9\0S77&myprofile/classes/output/myprofile.phpnu[. /** * Class containing data for myprofile block. * * @package block_myprofile * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_myprofile\output; defined('MOODLE_INTERNAL') || die(); use renderable; use renderer_base; use templatable; /** * Class containing data for myprofile block. * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class myprofile implements renderable, templatable { /** * @var object An object containing the configuration information for the current instance of this block. */ protected $config; /** * Constructor. * * @param object $config An object containing the configuration information for the current instance of this block. */ public function __construct($config) { $this->config = $config; } /** * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output * @return \stdClass */ public function export_for_template(renderer_base $output) { global $USER, $OUTPUT; $data = new \stdClass(); if (!isset($this->config->display_picture) || $this->config->display_picture == 1) { $data->userpicture = $OUTPUT->user_picture($USER, array('class' => 'userpicture')); } $data->userfullname = fullname($USER); if (!isset($this->config->display_country) || $this->config->display_country == 1) { $countries = get_string_manager()->get_list_of_countries(true); if (isset($countries[$USER->country])) { $data->usercountry = $countries[$USER->country]; } } if (!isset($this->config->display_city) || $this->config->display_city == 1) { $data->usercity = $USER->city; } if (!isset($this->config->display_email) || $this->config->display_email == 1) { $data->useremail = obfuscate_mailto($USER->email, ''); } if (!empty($this->config->display_phone1) && !empty($USER->phone1)) { $data->userphone1 = s($USER->phone1); } if (!empty($this->config->display_phone2) && !empty($USER->phone2)) { $data->userphone2 = s($USER->phone2); } if (!empty($this->config->display_institution) && !empty($USER->institution)) { $data->userinstitution = format_string($USER->institution); } if (!empty($this->config->display_address) && !empty($USER->address)) { $data->useraddress = format_string($USER->address); } if (!empty($this->config->display_idnumber) && !empty($USER->idnumber)) { $data->useridnumber = s($USER->idnumber); } if (!empty($this->config->display_firstaccess) && !empty($USER->firstaccess)) { $data->userfirstaccess = userdate($USER->firstaccess); } if (!empty($this->config->display_lastaccess) && !empty($USER->lastaccess)) { $data->userlastaccess = userdate($USER->lastaccess); } if (!empty($this->config->display_currentlogin) && !empty($USER->currentlogin)) { $data->usercurrentlogin = userdate($USER->currentlogin); } if (!empty($this->config->display_lastip) && !empty($USER->lastip)) { $data->userlastip = $USER->lastip; } if (!empty($this->config->display_lastlogin) && !empty($USER->lastlogin)) { $data->userlastlogin = userdate($USER->lastlogin); } return $data; } } PK'9\m'&myprofile/templates/myprofile.mustachenu[{{! 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 . }} {{! @template block_myprofile/myprofile This template renders the content of the myprofile block. Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * userfullname Optional context variables for this template: * userpicture * usercountry * usercity * useremail * userphone1 * userphone2 * userinstitution * useraddress * useridnumber * userfirstaccess * userlastaccess * usercurrentlogin * userlastip * userlastlogin Example context (json): { "userpicture": "", "userfullname": "John Doe", "usercountry": "Australia", "usercity": "Perth", "useremail": "john.doe@example.com", "userphone1": "123456789", "userphone2": "123456789", "userinstitution": "Institution", "useraddress": "Address", "useridnumber": "12345", "userfirstaccess": "Friday, 6 July 2018, 9:03 AM", "userlastaccess": "Wednesday, 26 September 2018, 8:05 AM", "usercurrentlogin": "Wednesday, 26 September 2018, 7:17 AM", "userlastip": "0:0:0:0:0:0:0:1", "userlastlogin": "Wednesday, 25 September 2018, 9:01 AM" } }}
              {{#userpicture}}
              {{{ userpicture }}}
              {{/userpicture}}
              {{ userfullname }}
              {{#usercountry}}
              {{#str}} country {{/str}}: {{ usercountry }}
              {{/usercountry}} {{#usercity}}
              {{#str}} city {{/str}}: {{ usercity }}
              {{/usercity}} {{#useremail}}
              {{#str}} email {{/str}}: {{{ useremail }}}
              {{/useremail}} {{#userphone1}}
              {{#str}} phone1 {{/str}}: {{ userphone1 }}
              {{/userphone1}} {{#userphone2}}
              {{#str}} phone2 {{/str}}: {{ userphone2 }}
              {{/userphone2}} {{#userinstitution}}
              {{#str}} institution {{/str}}: {{ userinstitution }}
              {{/userinstitution}} {{#useraddress}}
              {{#str}} address {{/str}}: {{ useraddress }}
              {{/useraddress}} {{#useridnumber}}
              {{#str}} idnumber {{/str}}: {{{ useridnumber }}}
              {{/useridnumber}} {{#userfirstaccess}}
              {{#str}} firstaccess {{/str}}: {{ userfirstaccess }}
              {{/userfirstaccess}} {{#userlastaccess}}
              {{#str}} lastaccess {{/str}}: {{ userlastaccess }}
              {{/userlastaccess}} {{#usercurrentlogin}}
              {{#str}} login {{/str}}: {{ usercurrentlogin }}
              {{/usercurrentlogin}} {{#userlastlogin}}
              {{#str}} lastlogin {{/str}}: {{ userlastlogin }}
              {{/userlastlogin}} {{#userlastip}}
              IP: {{ userlastip }}
              {{/userlastip}}
              PK'9\L[7myprofile/tests/behat/block_myprofile_frontpage.featurenu[@block @block_myprofile Feature: The logged in user block allows users to view their profile information on the front page In order to enable the logged in user block on the frontpage As an admin I can add the logged in user block to the frontpage and view my information Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | One | teacher1@example.com | T1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | myprofile | System | 1 | site-index | side-pre | Scenario: Try to view the logged in user block as a guest Given I log in as "guest" When I am on site homepage Then I should not see "Logged in user" Scenario: View the logged in user block by a logged in user Given I log in as "teacher1" When I am on site homepage Then I should see "Teacher One" in the "Logged in user" "block" PK'9\p7u..-myprofile/tests/behat/block_myprofile.featurenu[@block @block_myprofile Feature: The logged in user block allows users to view their profile information In order to enable the logged in user block As a user I can add the logged in user block and configure it to show my information Scenario: Configure the logged in user block to show / hide the users country Given the following "users" exist: | username | firstname | lastname | email | country | | teacher1 | Teacher | One | teacher1@example.com | AU | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display country | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "Australia" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display country | Yes | And I press "Save changes" And I should see "Australia" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users city Given the following "users" exist: | username | firstname | lastname | email | city | | teacher1 | Teacher | One | teacher1@example.com | Perth | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display city | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "Perth" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display city | Yes | And I press "Save changes" And I should see "Perth" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users email Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display email | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "teacher1@example.com" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display email | Yes | And I press "Save changes" And I should see "teacher1@example.com" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users phone Given the following "users" exist: | username | firstname | lastname | email | phone1 | | teacher1 | Teacher | One | teacher1@example.com | 555-5555 | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display phone | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "555-5555" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display phone | Yes | And I press "Save changes" And I should see "555-5555" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users mobile phone Given the following "users" exist: | username | firstname | lastname | email | phone2 | | teacher1 | Teacher | One | teacher1@example.com | 555-5555 | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display mobile phone | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "555-5555" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display mobile phone | Yes | And I press "Save changes" And I should see "555-5555" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users Institution Given the following "users" exist: | username | firstname | lastname | email | institution | | teacher1 | Teacher | One | teacher1@example.com | myinstitution | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display institution | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "myinstitution" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display institution | Yes | And I press "Save changes" And I should see "myinstitution" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users address Given the following "users" exist: | username | firstname | lastname | email | address | | teacher1 | Teacher | One | teacher1@example.com | myaddress | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display address | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "myaddress" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display address | Yes | And I press "Save changes" And I should see "myaddress" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users first access Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display first access | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "First access:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display first access | Yes | And I press "Save changes" And I should see "First access:" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users last access Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display last access | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "Last access:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display last access | Yes | And I press "Save changes" And I should see "Last access:" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users current login Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display current login | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "Log in:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display current login | Yes | And I press "Save changes" And I should see "Log in:" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users last ip Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display last IP | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "IP:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display last IP | Yes | And I press "Save changes" And I should see "IP:" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users idnumber Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | One | teacher1@example.com | ID12345 | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display ID number | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "ID number:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display ID number | Yes | And I press "Save changes" And I should see "ID number:" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users last login Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display last login | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "Last login:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display last login | Yes | And I press "Save changes" And I log out And I log in as "teacher1" And I should see "Last login:" in the "Logged in user" "block" PK'9\\4myprofile/tests/behat/block_myprofile_course.featurenu[@block @block_myprofile Feature: The logged in user block allows users to view their profile information in a course In order to enable the logged in user block in a course As a teacher I can add the logged in user block to a course and view my information Scenario: View the logged in user block by a user in a course Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | One | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Logged in user" block Then I should see "Teacher One" in the "Logged in user" "block" PK'9\͊C  6myprofile/tests/behat/block_myprofile_activity.featurenu[@block @block_myprofile Feature: The logged in user block allows users to view their profile information in an activity In order to enable the logged in user block in an activity As a teacher I can add the logged in user block to an activity and view my information Scenario: View the logged in user block by a user in an activity Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | One | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | course | idnumber | name | intro | | page | C1 | page1 | Test page name | Test page description | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | myprofile | Activity module | page1 | mod-page-* | side-pre | When I am on the "Test page name" "page activity" page logged in as teacher1 Then I should see "Teacher One" in the "Logged in user" "block" PK'9\LnY7myprofile/tests/behat/block_myprofile_dashboard.featurenu[@block @block_myprofile Feature: The logged in user block allows users to view their profile information in on the dashboard In order to enable the logged in user block on the dashboard As a user I can add the logged in user block to a the dashboard and view my information Scenario: View the logged in user block by a user on the dashboard Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block Then I should see "Teacher One" in the "Logged in user" "block" PK'9\pbadges/db/access.phpnu[. /** * Latest badges block capabilities. * * @package block_badges * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Yuliya Bozhko */ $capabilities = array( 'block/badges:addinstance' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), 'block/badges:myaddinstance' => array( 'riskbitmask' => RISK_PERSONAL, 'captype' => 'read', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW, ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), );PK'9\H#Gbadges/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the badges block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @since Moodle 2.8 * @package block_badges * @copyright 2014 Andrew Davis * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade the badges block * @param int $oldversion * @param object $block */ function xmldb_block_badges_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\_rbadges/lang/en/block_badges.phpnu[. /** * Language file for block "badges" * * @package block_badges * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Yuliya Bozhko */ $string['pluginname'] = 'Latest badges'; $string['numbadgestodisplay'] = 'Number of latest badges to display'; $string['nothingtodisplay'] = 'You have no badges to display'; $string['badges:addinstance'] = 'Add a new Latest badges block'; $string['badges:myaddinstance'] = 'Add a new Latest badges block to Dashboard'; $string['privacy:metadata'] = 'The Latest badges block only shows data stored in other locations.'; PK'9\O5jbadges/edit_form.phpnu[. /** * Form for editing badges block instances. * * @package block_badges * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Yuliya Bozhko */ class block_badges_edit_form extends block_edit_form { protected function specific_definition($mform) { $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $numberofbadges = array('0' => get_string('all')); for ($i = 1; $i <= 20; $i++) { $numberofbadges[$i] = $i; } $mform->addElement('select', 'config_numberofbadges', get_string('numbadgestodisplay', 'block_badges'), $numberofbadges); $mform->setDefault('config_numberofbadges', 10); } }PK'9\W^ ^ badges/block_badges.phpnu[. /** * Block for displaying earned local badges to users * * @package block_badges * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Yuliya Bozhko */ class block_badges extends block_base { public function init() { global $CFG; require_once($CFG->libdir . "/badgeslib.php"); $this->title = get_string('pluginname', 'block_badges'); } public function instance_allow_multiple() { return true; } public function has_config() { return false; } public function instance_allow_config() { return true; } public function applicable_formats() { return array( 'admin' => false, 'site-index' => true, 'course-view' => true, 'mod' => false, 'my' => true ); } public function specialization() { if (empty($this->config->title)) { $this->title = get_string('pluginname', 'block_badges'); } else { $this->title = $this->config->title; } } public function get_content() { global $USER, $CFG; if ($this->content !== null) { return $this->content; } if (empty($this->config)) { $this->config = new stdClass(); } // Number of badges to display. if (!isset($this->config->numberofbadges)) { $this->config->numberofbadges = 10; } // Create empty content. $this->content = new stdClass(); $this->content->text = ''; if (empty($CFG->enablebadges)) { $this->content->text .= get_string('badgesdisabled', 'badges'); return $this->content; } $courseid = $this->page->course->id; if ($courseid == SITEID) { $courseid = null; } if ($badges = badges_get_user_badges($USER->id, $courseid, 0, $this->config->numberofbadges)) { $output = $this->page->get_renderer('core', 'badges'); $this->content->text = $output->print_badges_list($badges, $USER->id, true); } else { $this->content->text .= get_string('nothingtodisplay', 'block_badges'); } return $this->content; } /** * This block shouldn't be added to a page if the badges advanced feature is disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { global $CFG; return $CFG->enablebadges; } } PK'9\7badges/version.phpnu[. /** * Version details * * @package block_badges * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Yuliya Bozhko */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_badges'; PK'9\jn#badges/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_badges. * * @package block_badges * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_badges\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_badges implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\ DDbadges/tests/fixtures/badge.pngnu[PNG  IHDRddpT IDATxctl`Ƕm۶fcc<6_]۶{mNϋcdl&ݝ1x֪ΪUsVͩ*Fx<ϡ Svx EXqaW?Yφ_Nz6ӟw8.m<= CXka*Fx)lzD80pc-G69<-5 !mrc>迆ӍF%=6,R }[?A e "PN7vy?v@b7HglgҍGf<vce3BglEf=Z:w1-%aED.}>l_(*?Sje(#l1? \P(cթVoryuߘfvЬPj ~ԙK+6]d~0s/"+/`/u.|AOzAY@hЍXPFJe=1tH׼@(FJI.)SjCIu3°iTfW+|W%MBEOS~ZY!4e7Ss~'np'd*s}IcbnMeŻXU w l}ҼʤL'Ѫt+Mݮ~s^ ez#,v pA(k.Fi~Me.}hh ~SxᰫTaҫh3 aafRxṂ6JFLeRRz ̓~[SO~`.+">gRm@`Gs!eT镝,s ]Zj]$NmK5&^:ZYgR$zsMCz 皬Jx)_CK7BEvFX1ʫ{?rram* *TmxK5X27CǒAm4͏̦G]ycoL:Hبvxa}2w ,)[kSyiY(Vݶ֦2+oUۜRkCN T2]_ ~[kC ~Uc卑̘TGkm(3@@=\Be@\^ ey~$Hd.}2 ??s@8a}FaRBe ӚN-2^zF T(#sd!uu~Y?D49dNx(ĩNS`sZvbIKۼ6gjX!z4̍%x%58 ^%ͭNGե;7'n 'wTh?C}wZgmp_jLc95[{+S52B4.n Ïm_hiK.}a`]:1] aƲ:?a34HyIH iZ ` 2H2P+ovT8ONvRYW0 $HXxhjJ0aae {֗!0Ւ o $MHI L?жX !qH߱!;f4+n8#tj_zWN?Tÿǥ2'$S}(TU1MwXIm9vk}]XE%G/.IENDB`PK'9\kCbadges/tests/badges_test.phpnu[. namespace block_badges; use advanced_testcase; use block_badges; use context_course; /** * PHPUnit block_badges tests * * @package block_badges * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_badges */ final class badges_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_badges.php'); parent::setUpBeforeClass(); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_badges(); // If badges advanced feature is enabled, the method should return true. set_config('enablebadges', true); $this->assertTrue($block->can_block_be_added($page)); // However, if the badges advanced feature is disabled, the method should return false. set_config('enablebadges', false); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\$'badges/tests/behat/block_badges.featurenu[@block @block_badges Feature: Enable Block Badges in a course without badges In order to view the badges block in a course As a teacher I can add badges block to a course and view the contents Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | Scenario: Add the block to a the course when badges are disabled Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Latest badges" block And the following config values are set as admin: | enablebadges | 0 | And I reload the page Then I should see "Badges are not enabled on this site." in the "Latest badges" "block" Scenario: Add the block to a the course when badges are enabled Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Latest badges" block Then I should see "You have no badges to display" in the "Latest badges" "block" PK'9\HQII1badges/tests/behat/block_badges_frontpage.featurenu[@block @block_badges @core_badges @_file_upload @javascript Feature: Enable Block Badges on the frontpage and view awarded badges In order to enable the badges block on the frontpage As a admin I can add badges block to the frontpage Scenario: Add the recent badges block on the frontpage and view recent badges Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | badges | System | 1 | site-index | side-pre | And the following "core_badges > Badge" exists: | name | Badge 1 | | course | C1 | | description | Badge 1 | | image | badges/tests/behat/badge.png | | status | active | | type | 2 | And the following "core_badges > Criteria" exists: | badge | Badge 1 | | role | editingteacher | And I am on the "Course 1" "course" page logged in as "teacher1" And I navigate to "Badges" in current page administration And I follow "Badge 1" And I select "Recipients (0)" from the "jump" singleselect And I press "Award badge" And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)" And I press "Award badge" When I am on site homepage Then I should see "Badge 1" in the "Latest badges" "block" PK'9\6i& .badges/tests/behat/block_badges_course.featurenu[@block @block_badges @core_badges @_file_upload @javascript Feature: Enable Block Badges in a course In order to enable the badges block in a course As a teacher I can add badges block to a course Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "core_badges > Badges" exist: | name | course | description | image | status | type | | Badge 1 | C1 | Badge 1 | badges/tests/behat/badge.png | active | 2 | | Badge 2 | C1 | Badge 2 | badges/tests/behat/badge.png | active | 2 | And the following "core_badges > Criterias" exist: | badge | role | | Badge 1 | editingteacher | | Badge 2 | editingteacher | And I am on the "Course 1" "course" page logged in as "teacher1" And I navigate to "Badges" in current page administration And I follow "Badge 1" And I select "Recipients (0)" from the "jump" singleselect And I press "Award badge" And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)" And I press "Award badge" # Issue Badge 2 of 2 And I am on "Course 1" course homepage And I navigate to "Badges" in current page administration And I follow "Badge 2" And I select "Recipients (0)" from the "jump" singleselect And I press "Award badge" And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)" And I press "Award badge" And I log out Scenario: Add the recent badges block to a course. Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Latest badges" block Then I should see "Badge 1" in the "Latest badges" "block" And I should see "Badge 2" in the "Latest badges" "block" Scenario: Add the recent badges block to a course and limit it to only display 1 badge. Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Latest badges" block And I configure the "Latest badges" block And I set the following fields to these values: | Number of latest badges to display | 1 | And I press "Save changes" Then I should see "Badge 2" in the "Latest badges" "block" And I should not see "Badge 1" in the "Latest badges" "block" PK'9\(@@1badges/tests/behat/block_badges_dashboard.featurenu[@block @block_badges @core_badges @_file_upload @javascript Feature: Enable Block Badges on the dashboard and view awarded badges In order to view recent badges on the dashboard As a teacher I can add badges block to the dashboard Scenario: Add the recent badges block to a course. Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | badges | System | 1 | my-index | side-post | And the following "core_badges > Badge" exists: | name | Badge 1 | | course | C1 | | description | Badge 1 | | image | badges/tests/behat/badge.png | | status | active | | type | 2 | And the following "core_badges > Criteria" exists: | badge | Badge 1 | | role | editingteacher | And I am on the "Course 1" "course" page logged in as "teacher1" And I navigate to "Badges" in current page administration And I follow "Badge 1" And I select "Recipients (0)" from the "jump" singleselect And I press "Award badge" And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)" And I press "Award badge" And I log out When I log in as "teacher1" Then I should see "Badge 1" in the "Latest badges" "block" PK'9\~ cmmfeedback/db/install.phpnu[. /** * Feedback block installation. * * @package block_feedback * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Perform the post-install procedures. */ function xmldb_block_feedback_install() { global $DB; // Disable feedback on new installs by default. $DB->set_field('block', 'visible', 0, ['name' => 'feedback']); } PK'9\A  feedback/db/access.phpnu[. /** * Feedback block caps. * * @package block_feedback * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/feedback:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\~feedback/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the feedback block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @package block_feedback * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Handles upgrading instances of this block. * * @param int $oldversion * @param object $block */ function xmldb_block_feedback_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\sL#feedback/lang/en/block_feedback.phpnu[. /** * Strings for component 'block_feedback', language 'en', branch 'MOODLE_20_STABLE' * * @package block_feedback * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['feedback'] = 'Feedback'; $string['feedback:addinstance'] = 'Add a new feedback block'; $string['pluginname'] = 'Feedback'; $string['privacy:metadata'] = 'The Feedback block only shows data stored in other locations.'; PK'9\P߭feedback/version.phpnu[. /** * Version details * * @package block_feedback * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_feedback'; // Full name of the plugin (used for diagnostics) $plugin->dependencies = ['mod_feedback' => 2025040800]; PK'9\hfL* * feedback/block_feedback.phpnu[. /** * Feedback block. * * @package block_feedback * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/mod/feedback/lib.php'); class block_feedback extends block_list { function init() { $this->title = get_string('feedback', 'block_feedback'); } function applicable_formats() { return array('site' => true, 'course' => true); } function get_content() { global $CFG, $OUTPUT; if ($this->content !== NULL) { return $this->content; } $this->content = new stdClass; $this->content->items = array(); $this->content->icons = array(); $this->content->footer = ''; $courseid = $this->page->course->id; if ($courseid <= 0) { $courseid = SITEID; } $icon = $OUTPUT->image_icon('monologo', get_string('pluginname', 'mod_feedback'), 'mod_feedback'); if (empty($this->instance->pageid)) { $this->instance->pageid = SITEID; } if ($feedbacks = feedback_get_feedbacks_from_sitecourse_map($courseid)) { $baseurl = new moodle_url('/mod/feedback/view.php'); foreach ($feedbacks as $feedback) { $url = new moodle_url($baseurl); $url->params(array('id'=>$feedback->cmid, 'courseid'=>$courseid)); $this->content->items[] = ''.$icon.$feedback->name.''; } } return $this->content; } } PK'9\J9%feedback/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_feedback. * * @package block_feedback * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_feedback\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_feedback implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\/JJ&recentlyaccessedcourses/db/install.phpnu[. /** * Recently accessed courses block installation. * * @package block_recentlyaccessedcourses * @copyright 2018 Victor Deniz based on code from 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Add the Recently accessed courses block to the dashboard for all users by default * when it is installed. */ function xmldb_block_recentlyaccessedcourses_install() { global $DB; if ($DB->count_records('block_instances') < 1) { // Only add the recentlyaccessedcourses block if it's being installed on an existing site. // For new sites it will be added by blocks_add_default_system_blocks(). return; } if ($defaultmypage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => 1))) { $subpagepattern = $defaultmypage->id; } else { $subpagepattern = null; } $page = new moodle_page(); $systemcontext = context_system::instance(); $page->set_context($systemcontext); // Add the block to the default /my. $page->blocks->add_region('content'); $page->blocks->add_block('recentlyaccessedcourses', 'content', 0, false, 'my-index', $subpagepattern); // Now we need to find all users that have viewed their dashboard because it'll have // made duplicates of the default block_instances for them so they won't see the new // recentlyaccessedcourses block without the admin resetting all of the dashboards. // // Instead we'll just add the recentlyaccessedcourses block to their dashboards here. $sql = "SELECT parentcontextid, subpagepattern FROM {block_instances} WHERE pagetypepattern = 'my-index' AND parentcontextid != ?"; $params = [$systemcontext->id]; $existingrecords = $DB->get_recordset_sql($sql, $params); $blockinstances = []; $seencontexts = []; $now = time(); foreach ($existingrecords as $existingrecord) { $parentcontextid = $existingrecord->parentcontextid; if (isset($seencontexts[$parentcontextid])) { // If we've seen this context already then skip it because we don't want // to add duplicate recentlyaccessedcourses blocks to the same context. This happens // if something funny is going on with the subpagepattern. continue; } else { $seencontexts[$parentcontextid] = true; } $blockinstances[] = [ 'blockname' => 'recentlyaccessedcourses', 'parentcontextid' => $parentcontextid, 'showinsubcontexts' => false, 'pagetypepattern' => 'my-index', 'subpagepattern' => $existingrecord->subpagepattern, 'defaultregion' => 'content', 'defaultweight' => 0, 'configdata' => '', 'timecreated' => $now, 'timemodified' => $now, ]; if (count($blockinstances) >= 1000) { // Insert after every 1000 records so that the memory usage doesn't // get out of control. $DB->insert_records('block_instances', $blockinstances); $blockinstances = []; } } $existingrecords->close(); if (!empty($blockinstances)) { // Insert what ever is left over. $DB->insert_records('block_instances', $blockinstances); } } PK'9\#%recentlyaccessedcourses/db/access.phpnu[. /** * Capabilities for the Recently accessed courses block. * * @package block_recentlyaccessedcourses * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/recentlyaccessedcourses:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ) ); PK'9\ uuArecentlyaccessedcourses/lang/en/block_recentlyaccessedcourses.phpnu[. /** * Strings for the Recently accessed courses block. * * @package block_recentlyaccessedcourses * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['displaycategories'] = 'Display categories'; $string['displaycategories_help'] = 'Display the course category on the recently accessed courses block items.'; $string['pluginname'] = 'Recently accessed courses'; $string['privacy:metadata'] = 'The Recently accessed courses block does not store any personal data.'; $string['recentlyaccessedcourses:myaddinstance'] = 'Add a new recently accessed courses block to Dashboard'; $string['nocourses'] = 'No recent courses'; PK'9\F'recentlyaccessedcourses/pix/courses.svgnu[ PK'9\#recentlyaccessedcourses/version.phpnu[. /** * Version details for the Recently accessed courses block. * * @package block_recentlyaccessedcourses * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_recentlyaccessedcourses'; // Full name of the plugin (used for diagnostics). PK'9\4recentlyaccessedcourses/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for Recently accessed courses block. * * @package block_recentlyaccessedcourses * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recentlyaccessedcourses\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for Recently accessed courses block. * * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\ ++/recentlyaccessedcourses/classes/output/main.phpnu[. /** * Class containing data for the Recently accessed courses block. * * @package block_recentlyaccessedcourses * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recentlyaccessedcourses\output; defined('MOODLE_INTERNAL') || die(); use renderable; use renderer_base; use templatable; /** * Class containing data for Recently accessed courses block. * * @package block_recentlyaccessedcourses * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class main implements renderable, templatable { /** * Export this data so it can be used as the context for a mustache template. * * @param renderer_base $output * @return \stdClass|array */ public function export_for_template(renderer_base $output) { global $USER; $nocoursesurl = $output->image_url('courses', 'block_recentlyaccessedcourses')->out(false); $config = get_config('block_recentlyaccessedcourses'); return [ 'userid' => $USER->id, 'nocoursesimgurl' => $nocoursesurl, 'pagingbar' => [ 'next' => true, 'previous' => true ], 'displaycategories' => !empty($config->displaycategories) ]; } } PK'9\Xtt3recentlyaccessedcourses/classes/output/renderer.phpnu[. /** * Recently accessed courses block renderer * * @package block_recentlyaccessedcourses * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recentlyaccessedcourses\output; defined('MOODLE_INTERNAL') || die; use plugin_renderer_base; /** * Recently accessed courses block renderer * * @package block_recentlyaccessedcourses * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends plugin_renderer_base { /** * Return the main content for the Recently accessed courses block. * * @param main $main The main renderable * @return string HTML string */ public function render_recentcourses(main $main) { return $this->render_from_template('block_recentlyaccessedcourses/main', $main->export_for_template($this)); } } PK'9\%,\\/recentlyaccessedcourses/templates/main.mustachenu[{{! 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 . }} {{! @template block_recentlyaccessedcourses/main This template renders the main content area for the Recently accessed courses block. Example context (json): { "userid": 2, "nocoursesimg": "https://moodlesite/theme/image.php/boost/block_recentlyaccessedcourses/1535727318/courses" } }}
              {{> block_recentlyaccessedcourses/recentlyaccessedcourses-view }}
              {{#js}} require( [ 'jquery', 'block_recentlyaccessedcourses/main' ], function( $, Main ) { var root = $('#block-recentlyaccessedcourses-{{uniqid}}'); var userid = root.attr('data-userid'); Main.init(userid, root); }); {{/js}}PK'9\o%~Grecentlyaccessedcourses/templates/recentlyaccessedcourses-view.mustachenu[{{! 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 . }} {{! @template block_recentlyaccessedcourses/recentlyaccessedcourses-view This template renders the courses view for the recentlyaccessedcourses block. Example context (json): { } }}
              {{> core_course/placeholder-course }}
              {{> core_course/placeholder-course }}
              {{> core_course/placeholder-course }}
              PK'9\8)##6recentlyaccessedcourses/templates/course-card.mustachenu[{{! 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 Licensebllsdsadfasfd along with Moodle. If not, see . }} {{! @template block_recentlyaccessedcourses/course-card This template renders a course for the recentlyaccessedcourses block. Example context (json): { "courses": [ { "name": "Assignment due 1", "viewurl": "https://moodlesite/course/view.php?id=2", "courseimageurl": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg", "fullname": "course 3", "isfavourite": true } ] } }} {{< core_course/coursecard }} {{$coursecategory}} {{#showcoursecategory}} {{#str}}aria:coursecategory, core_course{{/str}} {{{coursecategory}}} {{/showcoursecategory}} {{/coursecategory}} {{$coursename}} {{{fullname}}} {{/coursename}} {{$divider}} {{#showcoursecategory}}
              |
              {{/showcoursecategory}} {{/divider}} {{/ core_course/coursecard }} PK'9\֙c4 9recentlyaccessedcourses/block_recentlyaccessedcourses.phpnu[. /** * Class definition for the Recently accessed courses block. * * @package block_recentlyaccessedcourses * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Recently accessed courses block class. * * @package block_recentlyaccessedcourses * @copyright Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_recentlyaccessedcourses extends block_base { /** * Initialize class member variables */ public function init() { $this->title = get_string('pluginname', 'block_recentlyaccessedcourses'); } /** * Returns the contents. * * @return stdClass contents of block */ public function get_content() { if (isset($this->content)) { return $this->content; } $renderable = new block_recentlyaccessedcourses\output\main(); $renderer = $this->page->get_renderer('block_recentlyaccessedcourses'); $this->content = new stdClass(); $this->content->text = $renderer->render($renderable); $this->content->footer = ''; return $this->content; } /** * Locations where block can be displayed. * * @return array */ public function applicable_formats() { return array('my' => true); } /** * Allow the block to have a configuration page * * @return boolean */ public function has_config() { return true; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = get_config('block_recentlyaccessedcourses'); return (object) [ 'instance' => new stdClass(), 'plugin' => $configs, ]; } } PK'9\1 #recentlyaccessedcourses/upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This file describes API changes in the recentlyaccessedcourses block code. === 3.8 === * New admin setting block_recentlyaccessedcourses/displaycategories which enables the recently accessed courses block to display the course category. === 3.7 === * The 'block/recentlyaccessedcourses:addinstance' capability has been removed. It has never been used in code.PK'9\dњ88'recentlyaccessedcourses/amd/src/main.jsnu[// 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 . /** * Javascript to initialise the Recently accessed courses block. * * @module block_recentlyaccessedcourses/main * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'core/custom_interaction_events', 'core/notification', 'core/pubsub', 'core/paged_content_paging_bar', 'core/templates', 'core_course/events', 'core_course/repository', 'core/aria', ], function( $, CustomEvents, Notification, PubSub, PagedContentPagingBar, Templates, CourseEvents, CoursesRepository, Aria ) { // Constants. var NUM_COURSES_TOTAL = 10; var SELECTORS = { BLOCK_CONTAINER: '[data-region="recentlyaccessedcourses"]', CARD_CONTAINER: '[data-region="card-deck"]', COURSE_IS_FAVOURITE: '[data-region="is-favourite"]', CONTENT: '[data-region="view-content"]', EMPTY_MESSAGE: '[data-region="empty-message"]', LOADING_PLACEHOLDER: '[data-region="loading-placeholder"]', PAGING_BAR: '[data-region="paging-bar"]', PAGING_BAR_NEXT: '[data-control="next"]', PAGING_BAR_PREVIOUS: '[data-control="previous"]' }; // Module variables. var contentLoaded = false; var allCourses = []; var visibleCoursesId = null; var cardWidth = null; var viewIndex = 0; var availableVisibleCards = 1; /** * Show the empty message when no course are found. * * @param {object} root The root element for the courses view. */ var showEmptyMessage = function(root) { root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden'); root.find(SELECTORS.LOADING_PLACEHOLDER).addClass('hidden'); root.find(SELECTORS.CONTENT).addClass('hidden'); }; /** * Show the empty message when no course are found. * * @param {object} root The root element for the courses view. */ var showContent = function(root) { root.find(SELECTORS.CONTENT).removeClass('hidden'); root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden'); root.find(SELECTORS.LOADING_PLACEHOLDER).addClass('hidden'); }; /** * Show the paging bar. * * @param {object} root The root element for the courses view. */ var showPagingBar = function(root) { var pagingBar = root.find(SELECTORS.PAGING_BAR); pagingBar.css('opacity', 1); pagingBar.css('visibility', 'visible'); Aria.unhide(pagingBar); }; /** * Hide the paging bar. * * @param {object} root The root element for the courses view. */ var hidePagingBar = function(root) { var pagingBar = root.find(SELECTORS.PAGING_BAR); pagingBar.css('opacity', 0); pagingBar.css('visibility', 'hidden'); Aria.hide(pagingBar); }; /** * Show the favourite indicator for the given course (if it's in the list). * * @param {object} root The root element for the courses view. * @param {number} courseId The id of the course to be favourited. */ var favouriteCourse = function(root, courseId) { allCourses.forEach(function(course) { if (course.attr('data-course-id') == courseId) { course.find(SELECTORS.COURSE_IS_FAVOURITE).removeClass('hidden'); } }); }; /** * Hide the favourite indicator for the given course (if it's in the list). * * @param {object} root The root element for the courses view. * @param {number} courseId The id of the course to be unfavourited. */ var unfavouriteCourse = function(root, courseId) { allCourses.forEach(function(course) { if (course.attr('data-course-id') == courseId) { course.find(SELECTORS.COURSE_IS_FAVOURITE).addClass('hidden'); } }); }; /** * Render the a list of courses. * * @param {array} courses containing array of courses. * @return {promise} Resolved with list of rendered courses as jQuery objects. */ var renderAllCourses = function(courses) { var showcoursecategory = $(SELECTORS.BLOCK_CONTAINER).data('displaycoursecategory'); var promises = courses.map(function(course) { course.showcoursecategory = showcoursecategory; return Templates.render('block_recentlyaccessedcourses/course-card', course); }); return $.when.apply(null, promises).then(function() { var renderedCourses = []; promises.forEach(function(promise) { promise.then(function(html) { renderedCourses.push($(html)); return; }) .catch(Notification.exception); }); return renderedCourses; }); }; /** * Fetch user's recently accessed courses and reload the content of the block. * * @param {int} userid User whose courses will be shown * @returns {promise} The updated content for the block. */ var loadContent = function(userid) { return CoursesRepository.getLastAccessedCourses(userid, NUM_COURSES_TOTAL) .then(function(courses) { return renderAllCourses(courses); }); }; /** * Recalculate the number of courses that should be visible. * * @param {object} root The root element for the courses view. */ var recalculateVisibleCourses = function(root) { var container = root.find(SELECTORS.CONTENT).find(SELECTORS.CARD_CONTAINER); var availableWidth = parseFloat(root.css('width')); var numberOfCourses = allCourses.length; var start = 0; if (!cardWidth) { container.html(allCourses[0]); // Render one card initially to calculate the width of the cards // including the margins. cardWidth = allCourses[0].outerWidth(true); } availableVisibleCards = Math.floor(availableWidth / cardWidth); if (viewIndex + availableVisibleCards < numberOfCourses) { start = viewIndex; } else { var overflow = (viewIndex + availableVisibleCards) - numberOfCourses; start = viewIndex - overflow; start = start >= 0 ? start : 0; } // At least show one card. if (availableVisibleCards === 0) { availableVisibleCards = 1; } var coursesToShow = allCourses.slice(start, start + availableVisibleCards); // Create an id for the list of courses we expect to be displayed. var newVisibleCoursesId = coursesToShow.reduce(function(carry, course) { return carry + course.attr('data-course-id'); }, ''); // Centre the courses if we have an overflow of courses. if (allCourses.length > coursesToShow.length) { container.addClass('justify-content-center'); container.removeClass('justify-content-start'); } else { container.removeClass('justify-content-center'); container.addClass('justify-content-start'); } // Don't bother updating the DOM unless the visible courses have changed. if (visibleCoursesId != newVisibleCoursesId) { var pagingBar = root.find(PagedContentPagingBar.rootSelector); container.html(coursesToShow); visibleCoursesId = newVisibleCoursesId; if (availableVisibleCards >= allCourses.length) { hidePagingBar(root); } else { showPagingBar(root); if (viewIndex === 0) { PagedContentPagingBar.disablePreviousControlButtons(pagingBar); } else { PagedContentPagingBar.enablePreviousControlButtons(pagingBar); } if (viewIndex + availableVisibleCards >= allCourses.length) { PagedContentPagingBar.disableNextControlButtons(pagingBar); } else { PagedContentPagingBar.enableNextControlButtons(pagingBar); } } } }; /** * Register event listeners for the block. * * @param {object} root The root element for the recentlyaccessedcourses block. */ var registerEventListeners = function(root) { var resizeTimeout = null; var drawerToggling = false; PubSub.subscribe(CourseEvents.favourited, function(courseId) { favouriteCourse(root, courseId); }); PubSub.subscribe(CourseEvents.unfavorited, function(courseId) { unfavouriteCourse(root, courseId); }); PubSub.subscribe('nav-drawer-toggle-start', function() { if (!contentLoaded || !allCourses.length || drawerToggling) { // Nothing to recalculate. return; } drawerToggling = true; var recalculationCount = 0; // This function is going to recalculate the number of courses while // the nav drawer is opening or closes (up to a maximum of 5 recalcs). var doRecalculation = function() { setTimeout(function() { recalculateVisibleCourses(root); recalculationCount++; if (recalculationCount < 5 && drawerToggling) { // If we haven't done too many recalculations and the drawer // is still toggling then recurse. doRecalculation(); } }, 100); }; // Start the recalculations. doRecalculation(root); }); PubSub.subscribe('nav-drawer-toggle-end', function() { drawerToggling = false; }); $(window).on('resize', function() { if (!contentLoaded || !allCourses.length) { // Nothing to reclculate. return; } // Resize events fire rapidly so recalculating the visible courses each // time can be expensive. Let's debounce them, if (!resizeTimeout) { resizeTimeout = setTimeout(function() { resizeTimeout = null; recalculateVisibleCourses(root); // The recalculateVisibleCourses function will execute at a rate of 15fps. }, 66); } }); CustomEvents.define(root, [CustomEvents.events.activate]); root.on(CustomEvents.events.activate, SELECTORS.PAGING_BAR_NEXT, function(e, data) { var button = $(e.target).closest(SELECTORS.PAGING_BAR_NEXT); if (!button.hasClass('disabled')) { viewIndex = viewIndex + availableVisibleCards; recalculateVisibleCourses(root); } data.originalEvent.preventDefault(); }); root.on(CustomEvents.events.activate, SELECTORS.PAGING_BAR_PREVIOUS, function(e, data) { var button = $(e.target).closest(SELECTORS.PAGING_BAR_PREVIOUS); if (!button.hasClass('disabled')) { viewIndex = viewIndex - availableVisibleCards; viewIndex = viewIndex < 0 ? 0 : viewIndex; recalculateVisibleCourses(root); } data.originalEvent.preventDefault(); }); }; /** * Get and show the recent courses into the block. * * @param {int} userid User from which the courses will be obtained * @param {object} root The root element for the recentlyaccessedcourses block. */ var init = function(userid, root) { root = $(root); registerEventListeners(root); loadContent(userid) .then(function(renderedCourses) { allCourses = renderedCourses; contentLoaded = true; if (allCourses.length) { showContent(root); recalculateVisibleCourses(root); } else { showEmptyMessage(root); } return; }) .catch(Notification.exception); }; return { init: init }; }); PK'9\fKK1recentlyaccessedcourses/amd/build/main.min.js.mapnu[{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to initialise the Recently accessed courses block.\n *\n * @module block_recentlyaccessedcourses/main\n * @copyright 2018 Victor Deniz \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n [\n 'jquery',\n 'core/custom_interaction_events',\n 'core/notification',\n 'core/pubsub',\n 'core/paged_content_paging_bar',\n 'core/templates',\n 'core_course/events',\n 'core_course/repository',\n 'core/aria',\n ],\n function(\n $,\n CustomEvents,\n Notification,\n PubSub,\n PagedContentPagingBar,\n Templates,\n CourseEvents,\n CoursesRepository,\n Aria\n ) {\n\n // Constants.\n var NUM_COURSES_TOTAL = 10;\n var SELECTORS = {\n BLOCK_CONTAINER: '[data-region=\"recentlyaccessedcourses\"]',\n CARD_CONTAINER: '[data-region=\"card-deck\"]',\n COURSE_IS_FAVOURITE: '[data-region=\"is-favourite\"]',\n CONTENT: '[data-region=\"view-content\"]',\n EMPTY_MESSAGE: '[data-region=\"empty-message\"]',\n LOADING_PLACEHOLDER: '[data-region=\"loading-placeholder\"]',\n PAGING_BAR: '[data-region=\"paging-bar\"]',\n PAGING_BAR_NEXT: '[data-control=\"next\"]',\n PAGING_BAR_PREVIOUS: '[data-control=\"previous\"]'\n };\n // Module variables.\n var contentLoaded = false;\n var allCourses = [];\n var visibleCoursesId = null;\n var cardWidth = null;\n var viewIndex = 0;\n var availableVisibleCards = 1;\n\n /**\n * Show the empty message when no course are found.\n *\n * @param {object} root The root element for the courses view.\n */\n var showEmptyMessage = function(root) {\n root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');\n root.find(SELECTORS.LOADING_PLACEHOLDER).addClass('hidden');\n root.find(SELECTORS.CONTENT).addClass('hidden');\n };\n\n /**\n * Show the empty message when no course are found.\n *\n * @param {object} root The root element for the courses view.\n */\n var showContent = function(root) {\n root.find(SELECTORS.CONTENT).removeClass('hidden');\n root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');\n root.find(SELECTORS.LOADING_PLACEHOLDER).addClass('hidden');\n };\n\n /**\n * Show the paging bar.\n *\n * @param {object} root The root element for the courses view.\n */\n var showPagingBar = function(root) {\n var pagingBar = root.find(SELECTORS.PAGING_BAR);\n pagingBar.css('opacity', 1);\n pagingBar.css('visibility', 'visible');\n Aria.unhide(pagingBar);\n };\n\n /**\n * Hide the paging bar.\n *\n * @param {object} root The root element for the courses view.\n */\n var hidePagingBar = function(root) {\n var pagingBar = root.find(SELECTORS.PAGING_BAR);\n pagingBar.css('opacity', 0);\n pagingBar.css('visibility', 'hidden');\n Aria.hide(pagingBar);\n };\n\n /**\n * Show the favourite indicator for the given course (if it's in the list).\n *\n * @param {object} root The root element for the courses view.\n * @param {number} courseId The id of the course to be favourited.\n */\n var favouriteCourse = function(root, courseId) {\n allCourses.forEach(function(course) {\n if (course.attr('data-course-id') == courseId) {\n course.find(SELECTORS.COURSE_IS_FAVOURITE).removeClass('hidden');\n }\n });\n };\n\n /**\n * Hide the favourite indicator for the given course (if it's in the list).\n *\n * @param {object} root The root element for the courses view.\n * @param {number} courseId The id of the course to be unfavourited.\n */\n var unfavouriteCourse = function(root, courseId) {\n allCourses.forEach(function(course) {\n if (course.attr('data-course-id') == courseId) {\n course.find(SELECTORS.COURSE_IS_FAVOURITE).addClass('hidden');\n }\n });\n };\n\n /**\n * Render the a list of courses.\n *\n * @param {array} courses containing array of courses.\n * @return {promise} Resolved with list of rendered courses as jQuery objects.\n */\n var renderAllCourses = function(courses) {\n var showcoursecategory = $(SELECTORS.BLOCK_CONTAINER).data('displaycoursecategory');\n var promises = courses.map(function(course) {\n course.showcoursecategory = showcoursecategory;\n return Templates.render('block_recentlyaccessedcourses/course-card', course);\n });\n\n return $.when.apply(null, promises).then(function() {\n var renderedCourses = [];\n\n promises.forEach(function(promise) {\n promise.then(function(html) {\n renderedCourses.push($(html));\n return;\n })\n .catch(Notification.exception);\n });\n\n return renderedCourses;\n });\n };\n\n /**\n * Fetch user's recently accessed courses and reload the content of the block.\n *\n * @param {int} userid User whose courses will be shown\n * @returns {promise} The updated content for the block.\n */\n var loadContent = function(userid) {\n return CoursesRepository.getLastAccessedCourses(userid, NUM_COURSES_TOTAL)\n .then(function(courses) {\n return renderAllCourses(courses);\n });\n };\n\n /**\n * Recalculate the number of courses that should be visible.\n *\n * @param {object} root The root element for the courses view.\n */\n var recalculateVisibleCourses = function(root) {\n var container = root.find(SELECTORS.CONTENT).find(SELECTORS.CARD_CONTAINER);\n var availableWidth = parseFloat(root.css('width'));\n var numberOfCourses = allCourses.length;\n var start = 0;\n\n if (!cardWidth) {\n container.html(allCourses[0]);\n // Render one card initially to calculate the width of the cards\n // including the margins.\n cardWidth = allCourses[0].outerWidth(true);\n }\n\n availableVisibleCards = Math.floor(availableWidth / cardWidth);\n\n if (viewIndex + availableVisibleCards < numberOfCourses) {\n start = viewIndex;\n } else {\n var overflow = (viewIndex + availableVisibleCards) - numberOfCourses;\n start = viewIndex - overflow;\n start = start >= 0 ? start : 0;\n }\n\n // At least show one card.\n if (availableVisibleCards === 0) {\n availableVisibleCards = 1;\n }\n\n var coursesToShow = allCourses.slice(start, start + availableVisibleCards);\n // Create an id for the list of courses we expect to be displayed.\n var newVisibleCoursesId = coursesToShow.reduce(function(carry, course) {\n return carry + course.attr('data-course-id');\n }, '');\n\n // Centre the courses if we have an overflow of courses.\n if (allCourses.length > coursesToShow.length) {\n container.addClass('justify-content-center');\n container.removeClass('justify-content-start');\n } else {\n container.removeClass('justify-content-center');\n container.addClass('justify-content-start');\n }\n\n // Don't bother updating the DOM unless the visible courses have changed.\n if (visibleCoursesId != newVisibleCoursesId) {\n var pagingBar = root.find(PagedContentPagingBar.rootSelector);\n container.html(coursesToShow);\n visibleCoursesId = newVisibleCoursesId;\n\n if (availableVisibleCards >= allCourses.length) {\n hidePagingBar(root);\n } else {\n showPagingBar(root);\n\n if (viewIndex === 0) {\n PagedContentPagingBar.disablePreviousControlButtons(pagingBar);\n } else {\n PagedContentPagingBar.enablePreviousControlButtons(pagingBar);\n }\n\n if (viewIndex + availableVisibleCards >= allCourses.length) {\n PagedContentPagingBar.disableNextControlButtons(pagingBar);\n } else {\n PagedContentPagingBar.enableNextControlButtons(pagingBar);\n }\n }\n }\n };\n\n /**\n * Register event listeners for the block.\n *\n * @param {object} root The root element for the recentlyaccessedcourses block.\n */\n var registerEventListeners = function(root) {\n var resizeTimeout = null;\n var drawerToggling = false;\n\n PubSub.subscribe(CourseEvents.favourited, function(courseId) {\n favouriteCourse(root, courseId);\n });\n\n PubSub.subscribe(CourseEvents.unfavorited, function(courseId) {\n unfavouriteCourse(root, courseId);\n });\n\n PubSub.subscribe('nav-drawer-toggle-start', function() {\n if (!contentLoaded || !allCourses.length || drawerToggling) {\n // Nothing to recalculate.\n return;\n }\n\n drawerToggling = true;\n var recalculationCount = 0;\n // This function is going to recalculate the number of courses while\n // the nav drawer is opening or closes (up to a maximum of 5 recalcs).\n var doRecalculation = function() {\n setTimeout(function() {\n recalculateVisibleCourses(root);\n recalculationCount++;\n\n if (recalculationCount < 5 && drawerToggling) {\n // If we haven't done too many recalculations and the drawer\n // is still toggling then recurse.\n doRecalculation();\n }\n }, 100);\n };\n\n // Start the recalculations.\n doRecalculation(root);\n });\n\n PubSub.subscribe('nav-drawer-toggle-end', function() {\n drawerToggling = false;\n });\n\n $(window).on('resize', function() {\n if (!contentLoaded || !allCourses.length) {\n // Nothing to reclculate.\n return;\n }\n\n // Resize events fire rapidly so recalculating the visible courses each\n // time can be expensive. Let's debounce them,\n if (!resizeTimeout) {\n resizeTimeout = setTimeout(function() {\n resizeTimeout = null;\n recalculateVisibleCourses(root);\n // The recalculateVisibleCourses function will execute at a rate of 15fps.\n }, 66);\n }\n });\n\n CustomEvents.define(root, [CustomEvents.events.activate]);\n root.on(CustomEvents.events.activate, SELECTORS.PAGING_BAR_NEXT, function(e, data) {\n var button = $(e.target).closest(SELECTORS.PAGING_BAR_NEXT);\n if (!button.hasClass('disabled')) {\n viewIndex = viewIndex + availableVisibleCards;\n recalculateVisibleCourses(root);\n }\n\n data.originalEvent.preventDefault();\n });\n\n root.on(CustomEvents.events.activate, SELECTORS.PAGING_BAR_PREVIOUS, function(e, data) {\n var button = $(e.target).closest(SELECTORS.PAGING_BAR_PREVIOUS);\n if (!button.hasClass('disabled')) {\n viewIndex = viewIndex - availableVisibleCards;\n viewIndex = viewIndex < 0 ? 0 : viewIndex;\n recalculateVisibleCourses(root);\n }\n\n data.originalEvent.preventDefault();\n });\n };\n\n /**\n * Get and show the recent courses into the block.\n *\n * @param {int} userid User from which the courses will be obtained\n * @param {object} root The root element for the recentlyaccessedcourses block.\n */\n var init = function(userid, root) {\n root = $(root);\n\n registerEventListeners(root);\n loadContent(userid)\n .then(function(renderedCourses) {\n allCourses = renderedCourses;\n contentLoaded = true;\n\n if (allCourses.length) {\n showContent(root);\n recalculateVisibleCourses(root);\n } else {\n showEmptyMessage(root);\n }\n\n return;\n })\n .catch(Notification.exception);\n };\n\n return {\n init: init\n };\n });\n"],"names":["define","$","CustomEvents","Notification","PubSub","PagedContentPagingBar","Templates","CourseEvents","CoursesRepository","Aria","SELECTORS","contentLoaded","allCourses","visibleCoursesId","cardWidth","viewIndex","availableVisibleCards","loadContent","userid","getLastAccessedCourses","then","courses","showcoursecategory","data","promises","map","course","render","when","apply","renderedCourses","forEach","promise","html","push","catch","exception","renderAllCourses","recalculateVisibleCourses","root","container","find","availableWidth","parseFloat","css","numberOfCourses","length","start","outerWidth","Math","floor","coursesToShow","slice","newVisibleCoursesId","reduce","carry","attr","addClass","removeClass","pagingBar","rootSelector","hide","hidePagingBar","unhide","showPagingBar","disablePreviousControlButtons","enablePreviousControlButtons","disableNextControlButtons","enableNextControlButtons","registerEventListeners","resizeTimeout","drawerToggling","subscribe","favourited","courseId","favouriteCourse","unfavorited","unfavouriteCourse","recalculationCount","doRecalculation","setTimeout","window","on","events","activate","e","target","closest","hasClass","originalEvent","preventDefault","init","showContent","showEmptyMessage"],"mappings":";;;;;;;AAuBAA,4CACI,CACI,SACA,iCACA,oBACA,cACA,gCACA,iBACA,qBACA,yBACA,cAEJ,SACIC,EACAC,aACAC,aACAC,OACAC,sBACAC,UACAC,aACAC,kBACAC,UAKIC,0BACiB,0CADjBA,yBAEgB,4BAFhBA,8BAGqB,+BAHrBA,kBAIS,+BAJTA,wBAKe,gCALfA,8BAMqB,sCANrBA,qBAOY,6BAPZA,0BAQiB,wBARjBA,8BASqB,4BAGrBC,eAAgB,EAChBC,WAAa,GACbC,iBAAmB,KACnBC,UAAY,KACZC,UAAY,EACZC,sBAAwB,EA8GxBC,YAAc,SAASC,eAChBV,kBAAkBW,uBAAuBD,OAjI5B,IAkIfE,MAAK,SAASC,gBA9BA,SAASA,aACxBC,mBAAqBrB,EAAES,2BAA2Ba,KAAK,yBACvDC,SAAWH,QAAQI,KAAI,SAASC,eAChCA,OAAOJ,mBAAqBA,mBACrBhB,UAAUqB,OAAO,4CAA6CD,kBAGlEzB,EAAE2B,KAAKC,MAAM,KAAML,UAAUJ,MAAK,eACjCU,gBAAkB,UAEtBN,SAASO,SAAQ,SAASC,SACtBA,QAAQZ,MAAK,SAASa,MAClBH,gBAAgBI,KAAKjC,EAAEgC,UAG1BE,MAAMhC,aAAaiC,cAGjBN,mBAaIO,CAAiBhB,aAShCiB,0BAA4B,SAASC,UACjCC,UAAYD,KAAKE,KAAK/B,mBAAmB+B,KAAK/B,0BAC9CgC,eAAiBC,WAAWJ,KAAKK,IAAI,UACrCC,gBAAkBjC,WAAWkC,OAC7BC,MAAQ,GAEPjC,YACD0B,UAAUP,KAAKrB,WAAW,IAG1BE,UAAYF,WAAW,GAAGoC,YAAW,IAGzChC,sBAAwBiC,KAAKC,MAAMR,eAAiB5B,WAEhDC,UAAYC,sBAAwB6B,iBACpCE,MAAQhC,UAIRgC,OADAA,MAAQhC,WADQA,UAAYC,sBAAyB6B,mBAEpC,EAAIE,MAAQ,EAIH,IAA1B/B,wBACAA,sBAAwB,OAGxBmC,cAAgBvC,WAAWwC,MAAML,MAAOA,MAAQ/B,uBAEhDqC,oBAAsBF,cAAcG,QAAO,SAASC,MAAO7B,eACpD6B,MAAQ7B,OAAO8B,KAAK,oBAC5B,OAGC5C,WAAWkC,OAASK,cAAcL,QAClCN,UAAUiB,SAAS,0BACnBjB,UAAUkB,YAAY,2BAEtBlB,UAAUkB,YAAY,0BACtBlB,UAAUiB,SAAS,0BAInB5C,kBAAoBwC,oBAAqB,KACrCM,UAAYpB,KAAKE,KAAKpC,sBAAsBuD,cAChDpB,UAAUP,KAAKkB,eACftC,iBAAmBwC,oBAEfrC,uBAAyBJ,WAAWkC,OAlI5B,SAASP,UACrBoB,UAAYpB,KAAKE,KAAK/B,sBAC1BiD,UAAUf,IAAI,UAAW,GACzBe,UAAUf,IAAI,aAAc,UAC5BnC,KAAKoD,KAAKF,WA+HFG,CAAcvB,QA/IN,SAASA,UACrBoB,UAAYpB,KAAKE,KAAK/B,sBAC1BiD,UAAUf,IAAI,UAAW,GACzBe,UAAUf,IAAI,aAAc,WAC5BnC,KAAKsD,OAAOJ,WA6IJK,CAAczB,MAEI,IAAdxB,UACAV,sBAAsB4D,8BAA8BN,WAEpDtD,sBAAsB6D,6BAA6BP,WAGnD5C,UAAYC,uBAAyBJ,WAAWkC,OAChDzC,sBAAsB8D,0BAA0BR,WAEhDtD,sBAAsB+D,yBAAyBT,cAW3DU,uBAAyB,SAAS9B,UAC9B+B,cAAgB,KAChBC,gBAAiB,EAErBnE,OAAOoE,UAAUjE,aAAakE,YAAY,SAASC,WAlJjC,SAASnC,KAAMmC,UACjC9D,WAAWmB,SAAQ,SAASL,QACpBA,OAAO8B,KAAK,mBAAqBkB,UACjChD,OAAOe,KAAK/B,+BAA+BgD,YAAY,aAgJ3DiB,CAAgBpC,EAAMmC,aAG1BtE,OAAOoE,UAAUjE,aAAaqE,aAAa,SAASF,WAxIhC,SAASnC,KAAMmC,UACnC9D,WAAWmB,SAAQ,SAASL,QACpBA,OAAO8B,KAAK,mBAAqBkB,UACjChD,OAAOe,KAAK/B,+BAA+B+C,SAAS,aAsIxDoB,CAAkBtC,EAAMmC,aAG5BtE,OAAOoE,UAAU,2BAA2B,cACnC7D,eAAkBC,WAAWkC,SAAUyB,gBAK5CA,gBAAiB,MACbO,mBAAqB,EAGrBC,gBAAkB,WAClBC,YAAW,WACP1C,0BAA0BC,QAC1BuC,mBAEyB,GAAKP,gBAG1BQ,oBAEL,MAIPA,gBAAgBxC,UAGpBnC,OAAOoE,UAAU,yBAAyB,WACtCD,gBAAiB,KAGrBtE,EAAEgF,QAAQC,GAAG,UAAU,WACdvE,eAAkBC,WAAWkC,SAO7BwB,gBACDA,cAAgBU,YAAW,WACvBV,cAAgB,KAChBhC,0BAA0BC,QAE3B,SAIXrC,aAAaF,OAAOuC,KAAM,CAACrC,aAAaiF,OAAOC,WAC/C7C,KAAK2C,GAAGhF,aAAaiF,OAAOC,SAAU1E,2BAA2B,SAAS2E,EAAG9D,MAC5DtB,EAAEoF,EAAEC,QAAQC,QAAQ7E,2BACrB8E,SAAS,cACjBzE,WAAwBC,sBACxBsB,0BAA0BC,OAG9BhB,KAAKkE,cAAcC,oBAGvBnD,KAAK2C,GAAGhF,aAAaiF,OAAOC,SAAU1E,+BAA+B,SAAS2E,EAAG9D,MAChEtB,EAAEoF,EAAEC,QAAQC,QAAQ7E,+BACrB8E,SAAS,cAEjBzE,WADAA,WAAwBC,uBACA,EAAI,EAAID,UAChCuB,0BAA0BC,OAG9BhB,KAAKkE,cAAcC,2BA+BpB,CACHC,KAtBO,SAASzE,OAAQqB,MACxBA,KAAOtC,EAAEsC,MAET8B,uBAAuB9B,MACvBtB,YAAYC,QACPE,MAAK,SAASU,iBAEXnB,eAAgB,GADhBC,WAAakB,iBAGEgB,SApRT,SAASP,MACvBA,KAAKE,KAAK/B,mBAAmBgD,YAAY,UACzCnB,KAAKE,KAAK/B,yBAAyB+C,SAAS,UAC5ClB,KAAKE,KAAK/B,+BAA+B+C,SAAS,UAkRtCmC,CAAYrD,MACZD,0BAA0BC,OAjSnB,SAASA,MAC5BA,KAAKE,KAAK/B,yBAAyBgD,YAAY,UAC/CnB,KAAKE,KAAK/B,+BAA+B+C,SAAS,UAClDlB,KAAKE,KAAK/B,mBAAmB+C,SAAS,UAgS1BoC,CAAiBtD,SAKxBJ,MAAMhC,aAAaiC"}PK'9\6-recentlyaccessedcourses/amd/build/main.min.jsnu[/** * Javascript to initialise the Recently accessed courses block. * * @module block_recentlyaccessedcourses/main * @copyright 2018 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("block_recentlyaccessedcourses/main",["jquery","core/custom_interaction_events","core/notification","core/pubsub","core/paged_content_paging_bar","core/templates","core_course/events","core_course/repository","core/aria"],(function($,CustomEvents,Notification,PubSub,PagedContentPagingBar,Templates,CourseEvents,CoursesRepository,Aria){var SELECTORS_BLOCK_CONTAINER='[data-region="recentlyaccessedcourses"]',SELECTORS_CARD_CONTAINER='[data-region="card-deck"]',SELECTORS_COURSE_IS_FAVOURITE='[data-region="is-favourite"]',SELECTORS_CONTENT='[data-region="view-content"]',SELECTORS_EMPTY_MESSAGE='[data-region="empty-message"]',SELECTORS_LOADING_PLACEHOLDER='[data-region="loading-placeholder"]',SELECTORS_PAGING_BAR='[data-region="paging-bar"]',SELECTORS_PAGING_BAR_NEXT='[data-control="next"]',SELECTORS_PAGING_BAR_PREVIOUS='[data-control="previous"]',contentLoaded=!1,allCourses=[],visibleCoursesId=null,cardWidth=null,viewIndex=0,availableVisibleCards=1,loadContent=function(userid){return CoursesRepository.getLastAccessedCourses(userid,10).then((function(courses){return function(courses){var showcoursecategory=$(SELECTORS_BLOCK_CONTAINER).data("displaycoursecategory"),promises=courses.map((function(course){return course.showcoursecategory=showcoursecategory,Templates.render("block_recentlyaccessedcourses/course-card",course)}));return $.when.apply(null,promises).then((function(){var renderedCourses=[];return promises.forEach((function(promise){promise.then((function(html){renderedCourses.push($(html))})).catch(Notification.exception)})),renderedCourses}))}(courses)}))},recalculateVisibleCourses=function(root){var container=root.find(SELECTORS_CONTENT).find(SELECTORS_CARD_CONTAINER),availableWidth=parseFloat(root.css("width")),numberOfCourses=allCourses.length,start=0;(cardWidth||(container.html(allCourses[0]),cardWidth=allCourses[0].outerWidth(!0)),availableVisibleCards=Math.floor(availableWidth/cardWidth),viewIndex+availableVisibleCards=0?start:0;0===availableVisibleCards&&(availableVisibleCards=1);var coursesToShow=allCourses.slice(start,start+availableVisibleCards),newVisibleCoursesId=coursesToShow.reduce((function(carry,course){return carry+course.attr("data-course-id")}),"");if(allCourses.length>coursesToShow.length?(container.addClass("justify-content-center"),container.removeClass("justify-content-start")):(container.removeClass("justify-content-center"),container.addClass("justify-content-start")),visibleCoursesId!=newVisibleCoursesId){var pagingBar=root.find(PagedContentPagingBar.rootSelector);container.html(coursesToShow),visibleCoursesId=newVisibleCoursesId,availableVisibleCards>=allCourses.length?function(root){var pagingBar=root.find(SELECTORS_PAGING_BAR);pagingBar.css("opacity",0),pagingBar.css("visibility","hidden"),Aria.hide(pagingBar)}(root):(!function(root){var pagingBar=root.find(SELECTORS_PAGING_BAR);pagingBar.css("opacity",1),pagingBar.css("visibility","visible"),Aria.unhide(pagingBar)}(root),0===viewIndex?PagedContentPagingBar.disablePreviousControlButtons(pagingBar):PagedContentPagingBar.enablePreviousControlButtons(pagingBar),viewIndex+availableVisibleCards>=allCourses.length?PagedContentPagingBar.disableNextControlButtons(pagingBar):PagedContentPagingBar.enableNextControlButtons(pagingBar))}},registerEventListeners=function(root){var resizeTimeout=null,drawerToggling=!1;PubSub.subscribe(CourseEvents.favourited,(function(courseId){!function(root,courseId){allCourses.forEach((function(course){course.attr("data-course-id")==courseId&&course.find(SELECTORS_COURSE_IS_FAVOURITE).removeClass("hidden")}))}(0,courseId)})),PubSub.subscribe(CourseEvents.unfavorited,(function(courseId){!function(root,courseId){allCourses.forEach((function(course){course.attr("data-course-id")==courseId&&course.find(SELECTORS_COURSE_IS_FAVOURITE).addClass("hidden")}))}(0,courseId)})),PubSub.subscribe("nav-drawer-toggle-start",(function(){if(contentLoaded&&allCourses.length&&!drawerToggling){drawerToggling=!0;var recalculationCount=0,doRecalculation=function(){setTimeout((function(){recalculateVisibleCourses(root),++recalculationCount<5&&drawerToggling&&doRecalculation()}),100)};doRecalculation(root)}})),PubSub.subscribe("nav-drawer-toggle-end",(function(){drawerToggling=!1})),$(window).on("resize",(function(){contentLoaded&&allCourses.length&&(resizeTimeout||(resizeTimeout=setTimeout((function(){resizeTimeout=null,recalculateVisibleCourses(root)}),66)))})),CustomEvents.define(root,[CustomEvents.events.activate]),root.on(CustomEvents.events.activate,SELECTORS_PAGING_BAR_NEXT,(function(e,data){$(e.target).closest(SELECTORS_PAGING_BAR_NEXT).hasClass("disabled")||(viewIndex+=availableVisibleCards,recalculateVisibleCourses(root)),data.originalEvent.preventDefault()})),root.on(CustomEvents.events.activate,SELECTORS_PAGING_BAR_PREVIOUS,(function(e,data){$(e.target).closest(SELECTORS_PAGING_BAR_PREVIOUS).hasClass("disabled")||(viewIndex=(viewIndex-=availableVisibleCards)<0?0:viewIndex,recalculateVisibleCourses(root)),data.originalEvent.preventDefault()}))};return{init:function(userid,root){root=$(root),registerEventListeners(root),loadContent(userid).then((function(renderedCourses){contentLoaded=!0,(allCourses=renderedCourses).length?(!function(root){root.find(SELECTORS_CONTENT).removeClass("hidden"),root.find(SELECTORS_EMPTY_MESSAGE).addClass("hidden"),root.find(SELECTORS_LOADING_PLACEHOLDER).addClass("hidden")}(root),recalculateVisibleCourses(root)):function(root){root.find(SELECTORS_EMPTY_MESSAGE).removeClass("hidden"),root.find(SELECTORS_LOADING_PLACEHOLDER).addClass("hidden"),root.find(SELECTORS_CONTENT).addClass("hidden")}(root)})).catch(Notification.exception)}}})); //# sourceMappingURL=main.min.js.mapPK'9\/recentlyaccessedcourses/tests/generator/lib.phpnu[. /** * Recently accessed courses block data generator class. * * @package block_recentlyaccessedcourses * @category test * @copyright 2021 Sara Arjona * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_recentlyaccessedcourses_generator extends testing_block_generator { } PK'9\]Srecentlyaccessedcourses/tests/behat/block_recentlyaccessedcourses_dashboard.featurenu[@block @block_recentlyaccessedcourses @javascript Feature: The recently accessed courses block allows users to easily access their most recently accessed courses In order to access the most recently accessed courses As a user I can use the Recently accessed courses block in my dashboard Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And the following "categories" exist: | name | category | idnumber | | Category A | 0 | CATA | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | | Course 2 | C2 | 0 | | Course 3 | C3 | 0 | | Course 4 | C4 | CATA | | Course 5 | C5 | 0 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | recentlyaccessedcourses | System | 1 | my-index | content | Scenario: User has not accessed any course Given I log in as "student1" Then I should see "No recent courses" in the "Recently accessed courses" "block" Scenario: User has accessed two courses Given I log in as "student1" And I should not see "Course 1" in the "Recently accessed courses" "block" And I should not see "Course 2" in the "Recently accessed courses" "block" When I am on "Course 1" course homepage And I am on "Course 2" course homepage And I follow "Dashboard" And I change window size to "large" Then I should see "Course 1" in the "Recently accessed courses" "block" And I should see "Course 2" in the "Recently accessed courses" "block" And I should not see "Course 3" in the "Recently accessed courses" "block" And I should not see "Course 4" in the "Recently accessed courses" "block" And I should not see "Course 5" in the "Recently accessed courses" "block" Scenario: Show course category name Given the following config values are set as admin: | displaycategories | 1 | block_recentlyaccessedcourses | And I log in as "student1" And I am on "Course 1" course homepage And I am on "Course 4" course homepage And I follow "Dashboard" And I should see "Category 1" in the "Recently accessed courses" "block" And I should see "Category A" in the "Recently accessed courses" "block" Scenario: Hide course category name Given the following config values are set as admin: | displaycategories | 0 | block_recentlyaccessedcourses | And I log in as "student1" And I am on "Course 1" course homepage And I am on "Course 4" course homepage And I follow "Dashboard" And I should not see "Category 1" in the "Recently accessed courses" "block" And I should not see "Category A" in the "Recently accessed courses" "block" Scenario: Show short course name Given the following config values are set as admin: | courselistshortnames | 1 | And I log in as "student1" And I am on "Course 1" course homepage And I am on "Course 4" course homepage And I follow "Dashboard" And I should see "C1" in the "Recently accessed courses" "block" And I should see "C4" in the "Recently accessed courses" "block" Scenario: Hide short course name Given the following config values are set as admin: | courselistshortnames | 0 | And I log in as "student1" And I am on "Course 1" course homepage And I am on "Course 4" course homepage And I follow "Dashboard" And I should not see "C1" in the "Recently accessed courses" "block" And I should not see "C4" in the "Recently accessed courses" "block" PK'9\H$@@$recentlyaccessedcourses/settings.phpnu[. /** * Settings for the recentlyaccessedcourses block * * @package block_recentlyaccessedcourses * @copyright 2019 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { // Display Course Categories on the recently accessed courses block items. $settings->add(new admin_setting_configcheckbox( 'block_recentlyaccessedcourses/displaycategories', get_string('displaycategories', 'block_recentlyaccessedcourses'), get_string('displaycategories_help', 'block_recentlyaccessedcourses'), 1)); } PK'9\L'))admin_bookmarks/db/access.phpnu[. /** * Admin bookmarks block caps. * * @package block_admin_bookmarks * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/admin_bookmarks:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/admin_bookmarks:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\G1admin_bookmarks/lang/en/block_admin_bookmarks.phpnu[. /** * Strings for component 'block_admin_bookmarks', language 'en', branch 'MOODLE_20_STABLE' * * @package block_admin_bookmarks * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['admin_bookmarks:addinstance'] = 'Add a new admin bookmarks block'; $string['admin_bookmarks:myaddinstance'] = 'Add a new admin bookmarks block to Dashboard'; $string['pluginname'] = 'Admin bookmarks'; $string['privacy:metadata'] = 'The Admin bookmarks block only shows data stored in other locations.'; PK'9\^} admin_bookmarks/delete.phpnu[. /** * Delete admin bookmarks. * * @package block_admin_bookmarks * @copyright 2006 vinkmar * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require('../../config.php'); require_once($CFG->libdir.'/adminlib.php'); require_login(); $context = context_system::instance(); $PAGE->set_context($context); $adminroot = admin_get_root(false, false); // settings not required - only pages // We clean section with safe path here for compatibility with external pages that include a slash in their name. if ($section = optional_param('section', '', PARAM_SAFEPATH) and confirm_sesskey()) { if (get_user_preferences('admin_bookmarks')) { $bookmarks = explode(',', get_user_preferences('admin_bookmarks')); $key = array_search($section, $bookmarks); if ($key === false) { throw new \moodle_exception('nonexistentbookmark', 'admin'); die; } unset($bookmarks[$key]); $bookmarks = implode(',', $bookmarks); set_user_preference('admin_bookmarks', $bookmarks); $temp = $adminroot->locate($section); if ($temp instanceof admin_externalpage) { redirect($temp->url, get_string('bookmarkdeleted','admin')); } elseif ($temp instanceof admin_settingpage) { redirect($CFG->wwwroot . '/' . $CFG->admin . '/settings.php?section=' . $section); } else if ($temp instanceof admin_category) { redirect($CFG->wwwroot . '/' . $CFG->admin . '/category.php?category=' . $section); } else { redirect($CFG->wwwroot); } die; } throw new \moodle_exception('nobookmarksforuser', 'admin'); die; } else { throw new \moodle_exception('invalidsection', 'admin'); die; } PK'9\Dͫadmin_bookmarks/version.phpnu[. /** * Version details * * @package block_admin_bookmarks * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_admin_bookmarks'; // Full name of the plugin (used for diagnostics) PK'9\=,admin_bookmarks/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_admin_bookmarks. * * @package block_admin_bookmarks * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_admin_bookmarks\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_admin_bookmarks implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\ocͬ)admin_bookmarks/block_admin_bookmarks.phpnu[. /** * Admin Bookmarks Block page. * * @package block_admin_bookmarks * @copyright 2011 Moodle * @author 2006 vinkmar * 2011 Rossiani Wijaya (updated) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * The admin bookmarks block class */ class block_admin_bookmarks extends block_base { /** @var string */ public $blockname = null; /** @var bool|null */ protected $docked = null; /** * Set the initial properties for the block */ function init() { $this->blockname = get_class($this); $this->title = get_string('pluginname', $this->blockname); } /** * All multiple instances of this block * @return bool Returns false */ function instance_allow_multiple() { return false; } /** * Set the applicable formats for this block to all * @return array */ function applicable_formats() { if (has_capability('moodle/site:config', context_system::instance())) { return array('all' => true); } else { return array('site' => true); } } /** * Gets the content for this block */ function get_content() { global $CFG; // First check if we have already generated, don't waste cycles if ($this->content !== null) { return $this->content; } $this->content = new stdClass(); if (get_user_preferences('admin_bookmarks')) { require_once($CFG->libdir.'/adminlib.php'); $adminroot = admin_get_root(false, false); // settings not required - only pages $bookmarks = explode(',', get_user_preferences('admin_bookmarks')); /// Accessibility: markup as a list. $contents = array(); foreach($bookmarks as $bookmark) { $temp = $adminroot->locate($bookmark); if ($temp instanceof admin_settingpage) { $contenturl = new moodle_url('/admin/settings.php', array('section'=>$bookmark)); $contentlink = html_writer::link($contenturl, $temp->visiblename); $contents[] = html_writer::tag('li', $contentlink); } else if ($temp instanceof admin_externalpage) { $contenturl = new moodle_url($temp->url); $contentlink = html_writer::link($contenturl, $temp->visiblename); $contents[] = html_writer::tag('li', $contentlink); } else if ($temp instanceof admin_category) { $contenturl = new moodle_url('/admin/category.php', array('category' => $bookmark)); $contentlink = html_writer::link($contenturl, $temp->visiblename); $contents[] = html_writer::tag('li', $contentlink); } } $this->content->text = html_writer::tag('ol', implode('', $contents), array('class' => 'list')); } else { $bookmarks = array(); } $this->content->footer = ''; $this->page->settingsnav->initialise(); $node = $this->page->settingsnav->get('root', navigation_node::TYPE_SITE_ADMIN); if (!$node || !$node->contains_active_node()) { return $this->content; } $section = $node->find_active_node()->key; if ($section == 'search' || empty($section)){ // the search page can't be properly bookmarked at present $this->content->footer = ''; } else if (in_array($section, $bookmarks)) { $deleteurl = new moodle_url('/blocks/admin_bookmarks/delete.php', array('section'=>$section, 'sesskey'=>sesskey())); $this->content->footer = html_writer::link($deleteurl, get_string('unbookmarkthispage','admin')); } else { $createurl = new moodle_url('/blocks/admin_bookmarks/create.php', array('section'=>$section, 'sesskey'=>sesskey())); $this->content->footer = html_writer::link($createurl, get_string('bookmarkthispage','admin')); } return $this->content; } /** * Returns the role that best describes the admin bookmarks block. * * @return string */ public function get_aria_role() { return 'navigation'; } } PK'9\{ $8admin_bookmarks/tests/behat/bookmark_admin_pages.featurenu[@block @block_admin_bookmarks Feature: Add a bookmarks to an admin pages In order to speed up common tasks As an admin I need to add and access pages through bookmarks Background: Given I log in as "admin" And I navigate to "Server > Tasks > Scheduled tasks" in site administration And I click on "Bookmark this page" "link" in the "Admin bookmarks" "block" And I log out # Test bookmark functionality using the "User profile fields" page as our bookmark. Scenario: Admin page can be bookmarked Given I log in as "admin" And I navigate to "Users > Accounts > User profile fields" in site administration When I click on "Bookmark this page" "link" in the "Admin bookmarks" "block" Then I should see "User profile fields" in the "Admin bookmarks" "block" # See the existing bookmark is there too. And I should see "Scheduled tasks" in the "Admin bookmarks" "block" Scenario: Admin page can be accessed through bookmarks block Given I log in as "admin" And I navigate to "Notifications" in site administration And I click on "Scheduled tasks" "link" in the "Admin bookmarks" "block" # Verify that we are on the right page. Then I should see "Day of week" in the "admintable" "table" Scenario: Admin page can be removed from bookmarks Given I log in as "admin" And I navigate to "Notifications" in site administration And I click on "Scheduled tasks" "link" in the "Admin bookmarks" "block" When I click on "Unbookmark this page" "link" in the "Admin bookmarks" "block" Then I should see "Bookmark deleted" And I wait to be redirected And I should not see "Scheduled tasks" in the "Admin bookmarks" "block" PK'9\ j=0 admin_bookmarks/create.phpnu[. /** * Create admin bookmarks. * * @package block_admin_bookmarks * @copyright 2006 vinkmar * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require('../../config.php'); require_once($CFG->libdir.'/adminlib.php'); require_login(); $context = context_system::instance(); $PAGE->set_context($context); $adminroot = admin_get_root(false, false); // settings not required - only pages // We clean section with safe path here for compatibility with external pages that include a slash in their name. if ($section = optional_param('section', '', PARAM_SAFEPATH) and confirm_sesskey()) { if (get_user_preferences('admin_bookmarks')) { $bookmarks = explode(',', get_user_preferences('admin_bookmarks')); if (in_array($section, $bookmarks)) { throw new \moodle_exception('bookmarkalreadyexists', 'admin'); die; } } else { $bookmarks = array(); } $temp = $adminroot->locate($section); if ($temp instanceof admin_settingpage || $temp instanceof admin_externalpage || $temp instanceof admin_category) { $bookmarks[] = $section; $bookmarks = implode(',', $bookmarks); set_user_preference('admin_bookmarks', $bookmarks); } else { throw new \moodle_exception('invalidsection', 'admin'); die; } if ($temp instanceof admin_settingpage) { redirect($CFG->wwwroot . '/' . $CFG->admin . '/settings.php?section=' . $section); } elseif ($temp instanceof admin_externalpage) { redirect($temp->url); } else if ($temp instanceof admin_category) { redirect($CFG->wwwroot . '/' . $CFG->admin . '/category.php?category=' . $section); } } else { throw new \moodle_exception('invalidsection', 'admin'); die; } PK'9\!p!pmoodleblock.class.phpnu[. /** * This file contains the parent class for moodle blocks, block_base. * * @package core_block * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ /// Constants /** * Block type of list. Contents of block should be set as an associative array in the content object as items ($this->content->items). Optionally include footer text in $this->content->footer. */ define('BLOCK_TYPE_LIST', 1); /** * Block type of text. Contents of block should be set to standard html text in the content object as items ($this->content->text). Optionally include footer text in $this->content->footer. */ define('BLOCK_TYPE_TEXT', 2); /** * Block type of tree. $this->content->items is a list of tree_item objects and $this->content->footer is a string. */ define('BLOCK_TYPE_TREE', 3); /** * Class for describing a moodle block, all Moodle blocks derive from this class * * @author Jon Papaioannou * @package core_block */ class block_base { /** * Internal var for storing/caching translated strings * @var string $str */ var $str; /** * The title of the block to be displayed in the block title area. * @var string $title */ var $title = NULL; /** * The name of the block to be displayed in the block title area if the title is empty. * @var string arialabel */ var $arialabel = NULL; /** * The type of content that this block creates. Currently support options - BLOCK_TYPE_LIST, BLOCK_TYPE_TEXT * @var int $content_type */ var $content_type = BLOCK_TYPE_TEXT; /** * An object to contain the information to be displayed in the block. * @var stdClass|null $content */ var $content = NULL; /** * The initialized instance of this block object. * @var stdClass $instance */ var $instance = NULL; /** * The page that this block is appearing on. * @var moodle_page */ public $page = NULL; /** * This block's context. * @var context */ public $context = NULL; /** * An object containing the instance configuration information for the current instance of this block. * @var stdClass $config */ var $config = NULL; /** * How often the cronjob should run, 0 if not at all. * @var int $cron */ var $cron = NULL; /// Class Functions /** * Fake constructor to keep PHP5 happy * */ function __construct() { $this->init(); } /** * Function that can be overridden to do extra cleanup before * the database tables are deleted. (Called once per block, not per instance!) */ function before_delete() { } /** * Returns the block name, as present in the class name, * the database, the block directory, etc etc. * * @return string */ function name() { // Returns the block name, as present in the class name, // the database, the block directory, etc etc. $myname = strtolower(get_class($this)); return substr($myname, strpos($myname, '_') + 1); } /** * Parent class version of this function simply returns NULL * This should be implemented by the derived class to return * the content object. * * @return stdClass */ function get_content() { // This should be implemented by the derived class. return NULL; } /** * Returns the class $title var value. * * Intentionally doesn't check if a title is set. * This is already done in {@link _self_test()} * * @return string $this->title */ function get_title() { // Intentionally doesn't check if a title is set. This is already done in _self_test() return $this->title; } /** * Returns the class $content_type var value. * * Intentionally doesn't check if content_type is set. * This is already done in {@link _self_test()} * * @return int $this->content_type */ function get_content_type() { // Intentionally doesn't check if a content_type is set. This is already done in _self_test() return $this->content_type; } /** * Returns true or false, depending on whether this block has any content to display * and whether the user has permission to view the block * * @return bool */ function is_empty() { if ( !has_capability('moodle/block:view', $this->context) ) { return true; } $this->get_content(); return(empty($this->content->text) && empty($this->content->footer)); } /** * First sets the current value of $this->content to NULL * then calls the block's {@link get_content()} function * to set its value back. * * @return stdClass */ function refresh_content() { // Nothing special here, depends on content() $this->content = NULL; return $this->get_content(); } /** * Return a block_contents object representing the full contents of this block. * * This internally calls ->get_content(), and then adds the editing controls etc. * * You probably should not override this method, but instead override * {@link html_attributes()}, {@link formatted_contents()} or {@link get_content()}, * {@link hide_header()}, {@link (get_edit_controls)}, etc. * * @return block_contents|null a representation of the block, for rendering. * @since Moodle 2.0. */ public function get_content_for_output($output) { global $CFG; // We can exit early if the current user doesn't have the capability to view the block. if (!has_capability('moodle/block:view', $this->context)) { return null; } $bc = new block_contents($this->html_attributes()); $bc->attributes['data-block'] = $this->name(); $bc->blockinstanceid = $this->instance->id; $bc->blockpositionid = $this->instance->blockpositionid; if ($this->instance->visible) { $bc->content = $this->formatted_contents($output); if (!empty($this->content->footer)) { $bc->footer = $this->content->footer; } } else { $bc->add_class('invisibleblock'); } if (!$this->hide_header()) { $bc->title = $this->title; } if (empty($bc->title)) { $bc->arialabel = new lang_string('pluginname', get_class($this)); $this->arialabel = $bc->arialabel; } if ($this->page->user_is_editing() && $this->instance_can_be_edited()) { $bc->controls = $this->page->blocks->edit_controls($this); } else { // we must not use is_empty on hidden blocks if ($this->is_empty() && !$bc->controls) { return null; } } if (empty($CFG->allowuserblockhiding) || (empty($bc->content) && empty($bc->footer)) || !$this->instance_can_be_collapsed()) { $bc->collapsible = block_contents::NOT_HIDEABLE; } else if (get_user_preferences('block' . $bc->blockinstanceid . 'hidden', false)) { $bc->collapsible = block_contents::HIDDEN; } else { $bc->collapsible = block_contents::VISIBLE; } if ($this->instance_can_be_docked() && !$this->hide_header()) { $bc->dockable = true; } $bc->annotation = ''; // TODO MDL-19398 need to work out what to say here. return $bc; } /** * Return an object containing all the block content to be returned by external functions. * * If your block is returning formatted content or provide files for download, you should override this method to use the * \core_external\util::format_text, \core_external\util::format_string functions for formatting or external_util::get_area_files for files. * * @param core_renderer $output the rendered used for output * @return stdClass object containing the block title, central content, footer and linked files (if any). * @since Moodle 3.6 */ public function get_content_for_external($output) { $bc = new stdClass; $bc->title = null; $bc->content = null; $bc->contentformat = FORMAT_HTML; $bc->footer = null; $bc->files = []; if ($this->instance->visible) { $bc->content = $this->formatted_contents($output); if (!empty($this->content->footer)) { $bc->footer = $this->content->footer; } } if (!$this->hide_header()) { $bc->title = $this->title; } return $bc; } /** * Return the plugin config settings for external functions. * * In some cases the configs will need formatting or be returned only if the current user has some capabilities enabled. * * @return stdClass the configs for both the block instance and plugin (as object with name -> value) * @since Moodle 3.8 */ public function get_config_for_external() { return (object) [ 'instance' => new stdClass(), 'plugin' => new stdClass(), ]; } /** * Convert the contents of the block to HTML. * * This is used by block base classes like block_list to convert the structured * $this->content->list and $this->content->icons arrays to HTML. So, in most * blocks, you probaby want to override the {@link get_contents()} method, * which generates that structured representation of the contents. * * @param $output The core_renderer to use when generating the output. * @return string the HTML that should appearn in the body of the block. * @since Moodle 2.0. */ protected function formatted_contents($output) { $this->get_content(); $this->get_required_javascript(); if (!empty($this->content->text)) { return $this->content->text; } else { return ''; } } /** * Tests if this block has been implemented correctly. * Also, $errors isn't used right now * * @return boolean */ function _self_test() { // Tests if this block has been implemented correctly. // Also, $errors isn't used right now $errors = array(); $correct = true; if ($this->get_title() === NULL) { $errors[] = 'title_not_set'; $correct = false; } if (!in_array($this->get_content_type(), array(BLOCK_TYPE_LIST, BLOCK_TYPE_TEXT, BLOCK_TYPE_TREE))) { $errors[] = 'invalid_content_type'; $correct = false; } //following selftest was not working when roles&capabilities were used from block /* if ($this->get_content() === NULL) { $errors[] = 'content_not_set'; $correct = false; }*/ $formats = $this->applicable_formats(); if (empty($formats) || array_sum($formats) === 0) { $errors[] = 'no_formats'; $correct = false; } return $correct; } /** * Subclasses should override this and return true if the * subclass block has a settings.php file. * * @return boolean */ function has_config() { return false; } /** * Which page types this block may appear on. * * The information returned here is processed by the * {@link blocks_name_allowed_in_format()} function. Look there if you need * to know exactly how this works. * * Default case: everything except mod and tag. * * @return array page-type prefix => true/false. */ function applicable_formats() { // Default case: the block can be used in courses and site index, but not in activities return array('all' => true, 'mod' => false, 'tag' => false); } /** * Default return is false - header will be shown * @return boolean */ function hide_header() { return false; } /** * Return any HTML attributes that you want added to the outer
              that * of the block when it is output. * * Because of the way certain JS events are wired it is a good idea to ensure * that the default values here still get set. * I found the easiest way to do this and still set anything you want is to * override it within your block in the following way * * * function html_attributes() { * $attributes = parent::html_attributes(); * $attributes['class'] .= ' mynewclass'; * return $attributes; * } * * * @return array attribute name => value. */ function html_attributes() { $attributes = array( 'id' => 'inst' . $this->instance->id, 'class' => 'block_' . $this->name() . ' block', ); $ariarole = $this->get_aria_role(); if ($ariarole) { $attributes['role'] = $ariarole; } if ($this->hide_header()) { $attributes['class'] .= ' no-header'; } if ($this->instance_can_be_docked() && get_user_preferences('docked_block_instance_' . $this->instance->id, 0)) { $attributes['class'] .= ' dock_on_load'; } return $attributes; } /** * Set up a particular instance of this class given data from the block_insances * table and the current page. (See {@link block_manager::load_blocks()}.) * * @param stdClass $instance data from block_insances, block_positions, etc. * @param moodle_page $page the page this block is on. */ function _load_instance($instance, $page) { if (!empty($instance->configdata)) { $this->config = unserialize_object(base64_decode($instance->configdata)); } $this->instance = $instance; $this->context = context_block::instance($instance->id); $this->page = $page; $this->specialization(); } /** * Allows the block to load any JS it requires into the page. * * By default this function simply permits the user to dock the block if it is dockable. * * Left null as of MDL-64506. */ function get_required_javascript() { } /** * This function is called on your subclass right after an instance is loaded * Use this function to act on instance data just after it's loaded and before anything else is done * For instance: if your block will have different title's depending on location (site, course, blog, etc) */ function specialization() { // Just to make sure that this method exists. } /** * Is each block of this type going to have instance-specific configuration? * Normally, this setting is controlled by {@link instance_allow_multiple()}: if multiple * instances are allowed, then each will surely need its own configuration. However, in some * cases it may be necessary to provide instance configuration to blocks that do not want to * allow multiple instances. In that case, make this function return true. * I stress again that this makes a difference ONLY if {@link instance_allow_multiple()} returns false. * @return boolean */ function instance_allow_config() { return false; } /** * Are you going to allow multiple instances of each block? * If yes, then it is assumed that the block WILL USE per-instance configuration * @return boolean */ function instance_allow_multiple() { // Are you going to allow multiple instances of each block? // If yes, then it is assumed that the block WILL USE per-instance configuration return false; } /** * Serialize and store config data */ function instance_config_save($data, $nolongerused = false) { global $DB; $DB->update_record('block_instances', ['id' => $this->instance->id, 'configdata' => base64_encode(serialize($data)), 'timemodified' => time()]); } /** * Replace the instance's configuration data with those currently in $this->config; */ function instance_config_commit($nolongerused = false) { global $DB; $this->instance_config_save($this->config); } /** * Do any additional initialization you may need at the time a new block instance is created * @return boolean */ function instance_create() { return true; } /** * Copy any block-specific data when copying to a new block instance. * @param int $fromid the id number of the block instance to copy from * @return boolean */ public function instance_copy($fromid) { return true; } /** * Delete everything related to this instance if you have been using persistent storage other than the configdata field. * @return boolean */ function instance_delete() { return true; } /** * Allows the block class to have a say in the user's ability to edit (i.e., configure) blocks of this type. * The framework has first say in whether this will be allowed (e.g., no editing allowed unless in edit mode) * but if the framework does allow it, the block can still decide to refuse. * @return boolean */ function user_can_edit() { global $USER; if (has_capability('moodle/block:edit', $this->context)) { return true; } // The blocks in My Moodle are a special case. We want them to inherit from the user context. if (!empty($USER->id) && $this->instance->parentcontextid == $this->page->context->id // Block belongs to this page && $this->page->context->contextlevel == CONTEXT_USER // Page belongs to a user && $this->page->context->instanceid == $USER->id) { // Page belongs to this user return has_capability('moodle/my:manageblocks', $this->page->context); } return false; } /** * Allows the block class to have a say in the user's ability to create new instances of this block. * The framework has first say in whether this will be allowed (e.g., no adding allowed unless in edit mode) * but if the framework does allow it, the block can still decide to refuse. * This function has access to the complete page object, the creation related to which is being determined. * * @param moodle_page $page * @return boolean */ function user_can_addto($page) { global $CFG; require_once($CFG->dirroot . '/user/lib.php'); // List of formats this block supports. $formats = $this->applicable_formats(); // Check if user is trying to add blocks to their profile page. $userpagetypes = user_page_type_list($page->pagetype, null, null); if (array_key_exists($page->pagetype, $userpagetypes)) { $capability = 'block/' . $this->name() . ':addinstance'; return $this->has_add_block_capability($page, $capability) && has_capability('moodle/user:manageownblocks', $page->context); } // The blocks in My Moodle are a special case and use a different capability. $mypagetypes = my_page_type_list($page->pagetype); // Get list of possible my page types. if (array_key_exists($page->pagetype, $mypagetypes)) { // Ensure we are on a page with a my page type. // If the block cannot be displayed on /my it is ok if the myaddinstance capability is not defined. // Is 'my' explicitly forbidden? // If 'all' has not been allowed, has 'my' been explicitly allowed? if ((isset($formats['my']) && $formats['my'] == false) || (empty($formats['all']) && empty($formats['my']))) { // Block cannot be added to /my regardless of capabilities. return false; } else { $capability = 'block/' . $this->name() . ':myaddinstance'; return $this->has_add_block_capability($page, $capability) && has_capability('moodle/my:manageblocks', $page->context); } } // Check if this is a block only used on /my. unset($formats['my']); if (empty($formats)) { // Block can only be added to /my - return false. return false; } $capability = 'block/' . $this->name() . ':addinstance'; if ($this->has_add_block_capability($page, $capability) && has_capability('moodle/block:edit', $page->context)) { return true; } return false; } /** * Returns true if the user can add a block to a page. * * @param moodle_page $page * @param string $capability the capability to check * @return boolean true if user can add a block, false otherwise. */ private function has_add_block_capability($page, $capability) { // Check if the capability exists. if (!get_capability_info($capability)) { // Debug warning that the capability does not exist, but no more than once per page. static $warned = array(); if (!isset($warned[$this->name()])) { debugging('The block ' .$this->name() . ' does not define the standard capability ' . $capability , DEBUG_DEVELOPER); $warned[$this->name()] = 1; } // If the capability does not exist, the block can always be added. return true; } else { return has_capability($capability, $page->context); } } static function get_extra_capabilities() { return array('moodle/block:view', 'moodle/block:edit'); } /** * Can be overridden by the block to prevent the block from being dockable. * * @return bool * * Return false as per MDL-64506 */ public function instance_can_be_docked() { return false; } /** * If overridden and set to false by the block it will not be hidable when * editing is turned on. * * @return bool */ public function instance_can_be_hidden() { return true; } /** * If overridden and set to false by the block it will not be collapsible. * * @return bool */ public function instance_can_be_collapsed() { return true; } /** * If overridden and set to false by the block it will not be editable. * * @return bool */ public function instance_can_be_edited() { return true; } /** @callback callback functions for comments api */ public static function comment_template($options) { $ret = <<___picture___
              ___name___ - ___time___
              ___content___
              EOD; return $ret; } public static function comment_permissions($options) { return array('view'=>true, 'post'=>true); } public static function comment_url($options) { return null; } public static function comment_display($comments, $options) { return $comments; } public static function comment_add(&$comments, $options) { return true; } /** * Returns the aria role attribute that best describes this block. * * Region is the default, but this should be overridden by a block is there is a region child, or even better * a landmark child. * * Options are as follows: * - application * - landmark * - form * - navigation * - search * * Please do not use top-level landmark roles such as 'banner', 'complementary', 'contentinfo', or 'main'. Read more at * {@link https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/ ARIA Authoring Practices Guide - Landmark Regions} * * @return string */ public function get_aria_role() { return 'region'; } /** * This method can be overriden to add some extra checks to decide whether the block can be added or not to a page. * It doesn't need to do the standard capability checks as they will be performed by has_add_block_capability(). * This method is user agnostic. If you want to check if a user can add a block or not, you should use user_can_addto(). * * @param moodle_page $page The page where this block will be added. * @return bool Whether the block can be added or not to the given page. */ public function can_block_be_added(moodle_page $page): bool { return true; } } /** * Specialized class for displaying a block with a list of icons/text labels * * The get_content method should set $this->content->items and (optionally) * $this->content->icons, instead of $this->content->text. * * @author Jon Papaioannou * @package core_block */ class block_list extends block_base { var $content_type = BLOCK_TYPE_LIST; function is_empty() { if ( !has_capability('moodle/block:view', $this->context) ) { return true; } $this->get_content(); return (empty($this->content->items) && empty($this->content->footer)); } protected function formatted_contents($output) { $this->get_content(); $this->get_required_javascript(); if (!empty($this->content->items)) { return $output->list_block_contents($this->content->icons, $this->content->items); } else { return ''; } } function html_attributes() { $attributes = parent::html_attributes(); $attributes['class'] .= ' list_block'; return $attributes; } } /** * Specialized class for displaying a tree menu. * * The {@link get_content()} method involves setting the content of * $this->content->items with an array of {@link tree_item} * objects (these are the top-level nodes). The {@link tree_item::children} * property may contain more tree_item objects, and so on. The tree_item class * itself is abstract and not intended for use, use one of it's subclasses. * * Unlike {@link block_list}, the icons are specified as part of the items, * not in a separate array. * * @author Alan Trick * @package core_block * @internal this extends block_list so we get is_empty() for free */ class block_tree extends block_list { /** * @var int specifies the manner in which contents should be added to this * block type. In this case $this->content->items is used with * {@link tree_item}s. */ public $content_type = BLOCK_TYPE_TREE; /** * Make the formatted HTML ouput. * * Also adds the required javascript call to the page output. * * @param core_renderer $output * @return string HTML */ protected function formatted_contents($output) { // based of code in admin_tree global $PAGE; // TODO change this when there is a proper way for blocks to get stuff into head. static $eventattached; if ($eventattached===null) { $eventattached = true; } if (!$this->content) { $this->content = new stdClass; $this->content->items = array(); } $this->get_required_javascript(); $this->get_content(); $content = $output->tree_block_contents($this->content->items,array('class'=>'block_tree list')); return $content; } } PK'9\z}blog_recent/db/access.phpnu[. /** * Blog recent block caps. * * @package block_blog_recent * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/blog_recent:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\W s:kk)blog_recent/lang/en/block_blog_recent.phpnu[. /** * Strings for component 'block_blog_recent', language 'en', branch 'MOODLE_20_STABLE' * * @package block_blog_recent * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['blog_recent:addinstance'] = 'Add a new recent blog entries block'; $string['norecentblogentries'] = 'No recent entries'; $string['numentriestodisplay'] = 'Number of recent entries to display'; $string['pluginname'] = 'Recent blog entries'; $string['recentinterval'] = 'Interval of time considered "recent"'; $string['privacy:metadata'] = 'The Recent blog entries block only shows data stored in other locations.'; PK'9\{  blog_recent/edit_form.phpnu[. /** * Form for editing tag block instances. * * @package block_blog_recent * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Form for editing tag block instances. * * @package block_blog_recent * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_blog_recent_edit_form extends block_edit_form { protected function specific_definition($mform) { // Fields for editing HTML block title and contents. $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $numberofentries = array(); for ($i = 1; $i <= 20; $i++) { $numberofentries[$i] = $i; } $mform->addElement('select', 'config_numberofrecentblogentries', get_string('numentriestodisplay', 'block_blog_recent'), $numberofentries); $mform->setDefault('config_numberofrecentblogentries', 4); $intervals = array( 7200 => get_string('numhours', '', 2), 14400 => get_string('numhours', '', 4), 21600 => get_string('numhours', '', 6), 43200 => get_string('numhours', '', 12), 86400 => get_string('numhours', '', 24), 172800 => get_string('numdays', '', 2), 604800 => get_string('numdays', '', 7) ); $mform->addElement('select', 'config_recentbloginterval', get_string('recentinterval', 'block_blog_recent'), $intervals); $mform->setDefault('config_recentbloginterval', 86400); } } PK'9\=_blog_recent/version.phpnu[. /** * Version details * * @package block_blog_recent * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_blog_recent'; // Full name of the plugin (used for diagnostics) PK'9\dJ(blog_recent/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_blog_recent. * * @package block_blog_recent * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_blog_recent\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_blog_recent implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\t&blog_recent/tests/blog_recent_test.phpnu[. namespace block_blog_recent; use advanced_testcase; use block_blog_recent; use context_course; /** * PHPUnit block_blog_recent tests * * @package block_blog_recent * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_blog_recent */ final class blog_recent_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_blog_recent.php'); parent::setUpBeforeClass(); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_blog_recent(); // If blogs advanced feature is enabled, the method should return true. set_config('enableblogs', true); $this->assertTrue($block->can_block_be_added($page)); // However, if the blogs advanced feature is disabled, the method should return false. set_config('enableblogs', false); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\k~ 8blog_recent/tests/behat/block_blog_recent_course.featurenu[@block @block_blog_menu @block_blog_recent Feature: Students can use the recent blog entries block to view recent entries on a course page In order to enable the recent blog entries block a course page As a teacher I can add the recent blog entries block to a course page Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | blog_menu | Course | C1 | course-view-* | side-pre | | blog_recent | Course | C1 | course-view-* | side-pre | And the "multilang" filter is "on" And the "multilang" filter applies to "content and headings" Scenario: Students use the recent blog entries block to view blogs Given I am on the "Course 1" course page logged in as student1 And I follow "Add an entry about this course" When I set the following fields to these values: | Entry title | S1 First Blog RUSSIANENGLISH | | Blog entry body | This is my awesome blog! | And I press "Save changes" Then I should see "S1 First Blog ENGLISH" And I should see "This is my awesome blog!" And I am on "Course 1" course homepage And I should see "S1 First Blog ENGLISH" And I follow "S1 First Blog" And I should see "This is my awesome blog!" Scenario: Students only see a few entries in the recent blog entries block Given I am on the "Course 1" course page logged in as student1 And I follow "Add an entry about this course" # Blog 1 of 5 And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I am on "Course 1" course homepage And I follow "Add an entry about this course" # Blog 2 of 5 And I set the following fields to these values: | Entry title | S1 Second Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I should see "S1 Second Blog" And I should see "This is my awesome blog!" And I am on "Course 1" course homepage And I follow "Add an entry about this course" # Blog 3 of 5 And I set the following fields to these values: | Entry title | S1 Third Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I should see "S1 Third Blog" And I should see "This is my awesome blog!" And I am on "Course 1" course homepage And I follow "Add an entry about this course" # Blog 4 of 5 And I set the following fields to these values: | Entry title | S1 Fourth Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I should see "S1 Fourth Blog" And I should see "This is my awesome blog!" And I am on "Course 1" course homepage And I follow "Add an entry about this course" # Blog 5 of 5 And I set the following fields to these values: | Entry title | S1 Fifth Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I should see "S1 Fifth Blog" And I should see "This is my awesome blog!" When I am on "Course 1" course homepage And I should not see "S1 First Blog" And I should see "S1 Second Blog" And I should see "S1 Third Blog" And I should see "S1 Fourth Blog" And I should see "S1 Fifth Blog" And I follow "S1 Fifth Blog" And I should see "This is my awesome blog!" Then I log out And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I configure the "Recent blog entries" block And I set the following fields to these values: | config_numberofrecentblogentries | 2 | And I press "Save changes" And I should see "S1 Fourth Blog" And I should see "S1 Fifth Blog" PK'9\UH|:blog_recent/tests/behat/block_blog_recent_activity.featurenu[@block @block_blog_menu @mod_assign @block_blog_recent Feature: Students can use the recent blog entries block to view recent entries on an activity page In order to enable the recent blog entries block an activity page As a teacher I can add the recent blog entries block to an activity page Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | And the following "activity" exists: | activity | assign | | course | C1 | | idnumber | 0001 | | name | Test assignment 1 | | intro | Offline text | | section | 1 | | assignsubmission_file_enabled | 0 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | blog_menu | Activity module | 0001 | mod-assign-view | side-pre | | blog_recent | Activity module | 0001 | mod-assign-view | side-pre | Scenario: Students use the recent blog entries block to view blogs Given I am on the "Test assignment 1" "assign activity" page logged in as student1 And I follow "Add an entry about this Assignment" When I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" Then I should see "S1 First Blog" And I should see "This is my awesome blog!" And I follow "Test assignment 1" And I should see "S1 First Blog" And I follow "S1 First Blog" And I should see "This is my awesome blog!" Scenario: Students only see a few entries in the recent blog entries block Given I am on the "Test assignment 1" "assign activity" page logged in as student1 And I follow "Add an entry about this Assignment" # Blog 1 of 5 And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I follow "Test assignment 1" And I follow "Add an entry about this Assignment" # Blog 2 of 5 And I set the following fields to these values: | Entry title | S1 Second Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I should see "S1 Second Blog" And I should see "This is my awesome blog!" And I follow "Test assignment 1" And I follow "Add an entry about this Assignment" # Blog 3 of 5 And I set the following fields to these values: | Entry title | S1 Third Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I should see "S1 Third Blog" And I should see "This is my awesome blog!" And I follow "Test assignment 1" And I follow "Add an entry about this Assignment" # Blog 4 of 5 And I set the following fields to these values: | Entry title | S1 Fourth Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I should see "S1 Fourth Blog" And I should see "This is my awesome blog!" And I follow "Test assignment 1" And I follow "Add an entry about this Assignment" # Blog 5 of 5 And I set the following fields to these values: | Entry title | S1 Fifth Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I should see "S1 Fifth Blog" And I should see "This is my awesome blog!" When I follow "Test assignment 1" And I should not see "S1 First Blog" And I should see "S1 Second Blog" And I should see "S1 Third Blog" And I should see "S1 Fourth Blog" And I should see "S1 Fifth Blog" And I follow "S1 Fifth Blog" And I should see "This is my awesome blog!" Then I log out And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I follow "Test assignment 1" And I configure the "Recent blog entries" block And I set the following fields to these values: | config_numberofrecentblogentries | 2 | And I press "Save changes" And I should see "S1 Fourth Blog" And I should see "S1 Fifth Blog" PK'9\b3SS1blog_recent/tests/behat/block_blog_recent.featurenu[@block @block_blog_recent Feature: Feature: Users can use the recent blog entries block to view recent blog entries. In order to enable the recent blog entries in a course As a teacher I can add recent blog entries block to a course Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | Scenario: Add the recent blogs block to a course when blogs are disabled Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Recent blog entries" block And the following config values are set as admin: | enableblogs | 0 | And I reload the page Then I should see "Blogging is disabled!" in the "Recent blog entries" "block" Scenario: Add the recent blogs block to a course when there are not any blog posts Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Recent blog entries" block Then I should see "No recent entries" in the "Recent blog entries" "block" PK'9\s\w;blog_recent/tests/behat/block_blog_recent_frontpage.featurenu[@block @block_blog_recent Feature: Feature: Students can use the recent blog entries block to view recent entries on the frontpage In order to enable the recent blog entries block on the frontpage As an admin I can add the recent blog entries block to the frontpage Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | blog_recent | System | 1 | site-index | side-pre | And the following config values are set as admin: | unaddableblocks | | theme_boost| And I log in as "admin" And I am on site homepage And I turn editing mode on # TODO MDL-57120 site "Blogs" link not accessible without navigation block. And I add the "Navigation" block if not present And I log out Scenario: Students use the recent blog entries block to view blogs Given I log in as "student1" And I am on site homepage And I click on "Site blogs" "link" in the "Navigation" "block" And I follow "Add a new entry" When I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" Then I should see "S1 First Blog" And I should see "This is my awesome blog!" And I am on site homepage And I should see "S1 First Blog" And I follow "S1 First Blog" And I should see "This is my awesome blog!" Scenario: Students only see a few entries in the recent blog entries block Given I log in as "student1" And I am on site homepage And I click on "Site blogs" "link" in the "Navigation" "block" And I follow "Add a new entry" # Blog 1 of 5 And I set the following fields to these values: | Entry title | S1 First Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I follow "Add a new entry" # Blog 2 of 5 And I set the following fields to these values: | Entry title | S1 Second Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I should see "S1 Second Blog" And I should see "This is my awesome blog!" And I follow "Add a new entry" # Blog 3 of 5 And I set the following fields to these values: | Entry title | S1 Third Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I should see "S1 Third Blog" And I should see "This is my awesome blog!" And I follow "Add a new entry" # Blog 4 of 5 And I set the following fields to these values: | Entry title | S1 Fourth Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I wait "1" seconds And I should see "S1 Fourth Blog" And I should see "This is my awesome blog!" And I follow "Add a new entry" # Blog 5 of 5 And I set the following fields to these values: | Entry title | S1 Fifth Blog | | Blog entry body | This is my awesome blog! | And I press "Save changes" And I should see "S1 Fifth Blog" And I should see "This is my awesome blog!" When I am on site homepage And I should not see "S1 First Blog" And I should see "S1 Second Blog" And I should see "S1 Third Blog" And I should see "S1 Fourth Blog" And I should see "S1 Fifth Blog" And I follow "S1 Fifth Blog" And I should see "This is my awesome blog!" Then I log out And I log in as "admin" And I am on site homepage And I turn editing mode on And I configure the "Recent blog entries" block And I set the following fields to these values: | config_numberofrecentblogentries | 2 | And I press "Save changes" And I should see "S1 Fourth Blog" And I should see "S1 Fifth Blog" PK'9\z!blog_recent/block_blog_recent.phpnu[. /** * Recent Blog Entries Block page. * * @package block_blog_recent * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * This block simply outputs a list of links to recent blog entries, depending on * the context of the current page. */ class block_blog_recent extends block_base { function init() { $this->title = get_string('pluginname', 'block_blog_recent'); $this->content_type = BLOCK_TYPE_TEXT; } function applicable_formats() { return array('all' => true, 'my' => false, 'tag' => false); } function instance_allow_config() { return true; } function get_content() { global $CFG; if ($this->content !== NULL) { return $this->content; } // verify blog is enabled if (empty($CFG->enableblogs)) { $this->content = new stdClass(); $this->content->text = ''; if ($this->page->user_is_editing()) { $this->content->text = get_string('blogdisable', 'blog'); } return $this->content; } else if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL and (!isloggedin() or isguestuser())) { $this->content = new stdClass(); $this->content->text = ''; return $this->content; } require_once($CFG->dirroot .'/blog/lib.php'); require_once($CFG->dirroot .'/blog/locallib.php'); if (empty($this->config)) { $this->config = new stdClass(); } if (empty($this->config->recentbloginterval)) { $this->config->recentbloginterval = 8400; } if (empty($this->config->numberofrecentblogentries)) { $this->config->numberofrecentblogentries = 4; } $this->content = new stdClass(); $this->content->footer = ''; $this->content->text = ''; $context = $this->page->context; $url = new moodle_url('/blog/index.php'); $filter = array(); if ($context->contextlevel == CONTEXT_MODULE) { $filter['module'] = $context->instanceid; $a = new stdClass; $a->type = get_string('modulename', $this->page->cm->modname); $strview = get_string('viewallmodentries', 'blog', $a); $url->param('modid', $context->instanceid); } else if ($context->contextlevel == CONTEXT_COURSE) { $filter['course'] = $context->instanceid; $a = new stdClass; $a->type = get_string('course'); $strview = get_string('viewblogentries', 'blog', $a); $url->param('courseid', $context->instanceid); } else { $strview = get_string('viewsiteentries', 'blog'); } $filter['since'] = $this->config->recentbloginterval; $bloglisting = new blog_listing($filter); $entries = $bloglisting->get_entries(0, $this->config->numberofrecentblogentries, 4); if (!empty($entries)) { $entrieslist = array(); $viewblogurl = new moodle_url('/blog/index.php'); foreach ($entries as $entryid => $entry) { $viewblogurl->param('entryid', $entryid); $entrylink = html_writer::link($viewblogurl, shorten_text(format_string($entry->subject, true, ['context' => $context]))); $entrieslist[] = $entrylink; } $this->content->text .= html_writer::alist($entrieslist, array('class'=>'list')); $viewallentrieslink = html_writer::link($url, $strview); $this->content->text .= $viewallentrieslink; } else { $this->content->text .= get_string('norecentblogentries', 'block_blog_recent'); } } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = !empty($this->config) ? $this->config : new stdClass(); return (object) [ 'instance' => $configs, 'plugin' => new stdClass(), ]; } /** * This block shouldn't be added to a page if the blogs advanced feature is disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { global $CFG; return $CFG->enableblogs; } } PK'9\Bhtml/db/access.phpnu[. /** * HTML block caps. * * @package block_html * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/html:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/html:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\B1DDhtml/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the html block * * @since Moodle 2.0 * @package block_html * @copyright 2010 Dongsheng Cai * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade code for the HTML block. * * @param int $oldversion */ function xmldb_block_html_upgrade($oldversion) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\hhhtml/lang/en/block_html.phpnu[. /** * Strings for component 'block_html', language 'en', branch 'MOODLE_20_STABLE' * * @package block_html * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['allowadditionalcssclasses'] = 'Allow additional CSS classes'; $string['configallowadditionalcssclasses'] = 'Adds a configuration option to Text block instances allowing additional CSS classes to be set.'; $string['configclasses'] = 'Additional CSS classes'; $string['configclasses_help'] = 'The purpose of this configuration is to aid with theming by helping distinguish text blocks from each other. Any CSS classes entered here (space delimited) will be appended to the block\'s default classes.'; $string['configcontent'] = 'Content'; $string['configtitle'] = 'Text block title'; $string['html:addinstance'] = 'Add a new text block'; $string['html:myaddinstance'] = 'Add a new text block to Dashboard'; $string['newhtmlblock'] = '(new text block)'; $string['pluginname'] = 'Text'; $string['search:content'] = 'Text block content'; $string['privacy:metadata:block'] = 'The Text block stores all of its data within the block subsystem.'; PK'9\0q 5html/backup/moodle2/restore_html_block_task.class.phpnu[. /** * @package block_html * @subpackage backup-moodle2 * @copyright 2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Specialised restore task for the html block * (requires encode_content_links in some configdata attrs) * * TODO: Finish phpdocs */ class restore_html_block_task extends restore_block_task { protected function define_my_settings() { } protected function define_my_steps() { } public function get_fileareas() { return array('content'); } public function get_configdata_encoded_attributes() { return array('text'); // We need to encode some attrs in configdata } public static function define_decode_contents() { $contents = array(); $contents[] = new restore_html_block_decode_content('block_instances', 'configdata', 'block_instance'); return $contents; } public static function define_decode_rules() { return array(); } } /** * Specialised restore_decode_content provider that unserializes the configdata * field, to serve the configdata->text content to the restore_decode_processor * packaging it back to its serialized form after process */ class restore_html_block_decode_content extends restore_decode_content { protected $configdata; // Temp storage for unserialized configdata protected function get_iterator() { global $DB; // Build the SQL dynamically here $fieldslist = 't.' . implode(', t.', $this->fields); $sql = "SELECT t.id, $fieldslist FROM {" . $this->tablename . "} t JOIN {backup_ids_temp} b ON b.newitemid = t.id WHERE b.backupid = ? AND b.itemname = ? AND t.blockname = 'html'"; $params = array($this->restoreid, $this->mapping); return ($DB->get_recordset_sql($sql, $params)); } protected function preprocess_field($field) { $this->configdata = unserialize_object(base64_decode($field)); return isset($this->configdata->text) ? $this->configdata->text : ''; } protected function postprocess_field($field) { $this->configdata->text = $field; return base64_encode(serialize($this->configdata)); } } PK'9\ކW4html/backup/moodle2/backup_html_block_task.class.phpnu[. /** * @package block_html * @subpackage backup-moodle2 * @copyright 2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Specialised backup task for the html block * (requires encode_content_links in some configdata attrs) * * TODO: Finish phpdocs */ class backup_html_block_task extends backup_block_task { protected function define_my_settings() { } protected function define_my_steps() { } public function get_fileareas() { return array('content'); } public function get_configdata_encoded_attributes() { return array('text'); // We need to encode some attrs in configdata } public static function encode_content_links($content) { return $content; // No special encoding of links } } PK'9\  html/backup/moodle1/lib.phpnu[. /** * Provides support for the conversion of moodle1 backup to the moodle2 format * * @package block_html * @copyright 2012 Paul Nicholls * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Block conversion handler for html */ class moodle1_block_html_handler extends moodle1_block_handler { private $fileman = null; protected function convert_configdata(array $olddata) { global $CFG; require_once($CFG->libdir . '/db/upgradelib.php'); $instanceid = $olddata['id']; $contextid = $this->converter->get_contextid(CONTEXT_BLOCK, $olddata['id']); $decodeddata = base64_decode($olddata['configdata']); list($updated, $configdata) = upgrade_fix_serialized_objects($decodeddata); $configdata = unserialize_object($configdata); // get a fresh new file manager for this instance $this->fileman = $this->converter->get_file_manager($contextid, 'block_html'); // convert course files embedded in the block content $this->fileman->filearea = 'content'; $this->fileman->itemid = 0; $configdata->text = moodle1_converter::migrate_referenced_files($configdata->text ?? '', $this->fileman); $configdata->format = FORMAT_HTML; return base64_encode(serialize($configdata)); } protected function write_inforef_xml($newdata, $data) { $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/inforef.xml"); $this->xmlwriter->begin_tag('inforef'); $this->xmlwriter->begin_tag('fileref'); foreach ($this->fileman->get_fileids() as $fileid) { $this->write_xml('file', array('id' => $fileid)); } $this->xmlwriter->end_tag('fileref'); $this->xmlwriter->end_tag('inforef'); $this->close_xml_writer(); } } PK'9\-)'((html/edit_form.phpnu[. /** * Form for editing HTML block instances. * * @package block_html * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Form for editing HTML block instances. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_html_edit_form extends block_edit_form { protected function specific_definition($mform) { global $CFG; // Fields for editing HTML block title and contents. $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $mform->addElement('text', 'config_title', get_string('configtitle', 'block_html')); $mform->setType('config_title', PARAM_TEXT); $editoroptions = array('maxfiles' => EDITOR_UNLIMITED_FILES, 'noclean'=>true, 'context'=>$this->block->context); $mform->addElement('editor', 'config_text', get_string('configcontent', 'block_html'), null, $editoroptions); $mform->addRule('config_text', null, 'required', null, 'client'); $mform->setType('config_text', PARAM_RAW); // XSS is prevented when printing the block contents and serving files if (!empty($CFG->block_html_allowcssclasses)) { $mform->addElement('text', 'config_classes', get_string('configclasses', 'block_html')); $mform->setType('config_classes', PARAM_TEXT); $mform->addHelpButton('config_classes', 'configclasses', 'block_html'); } } function set_data($defaults) { if (!empty($this->block->config) && !empty($this->block->config->text)) { $text = $this->block->config->text; $draftid_editor = file_get_submitted_draft_itemid('config_text'); if (empty($text)) { $currenttext = ''; } else { $currenttext = $text; } $defaults->config_text['text'] = file_prepare_draft_area($draftid_editor, $this->block->context->id, 'block_html', 'content', 0, array('subdirs'=>true), $currenttext); $defaults->config_text['itemid'] = $draftid_editor; $defaults->config_text['format'] = $this->block->config->format ?? FORMAT_MOODLE; } else { $text = ''; } if (!$this->block->user_can_edit() && !empty($this->block->config->title)) { // If a title has been set but the user cannot edit it format it nicely $title = $this->block->config->title; $defaults->config_title = format_string($title, true, $this->page->context); // Remove the title from the config so that parent::set_data doesn't set it. unset($this->block->config->title); } // have to delete text here, otherwise parent::set_data will empty content // of editor unset($this->block->config->text); parent::set_data($defaults); // restore $text if (!isset($this->block->config)) { $this->block->config = new stdClass(); } $this->block->config->text = $text; if (isset($title)) { // Reset the preserved title $this->block->config->title = $title; } } /** * Display the configuration form when block is being added to the page * * @return bool */ public static function display_form_when_adding(): bool { return true; } } PK'9\' .,,html/block_html.phpnu[. use core_external\util as external_util; /** * Form for editing HTML block instances. * * @package block_html * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_html extends block_base { function init() { $this->title = get_string('pluginname', 'block_html'); } function has_config() { return true; } function applicable_formats() { return array('all' => true); } function specialization() { if (isset($this->config->title)) { $this->title = $this->title = format_string($this->config->title, true, ['context' => $this->context]); } else { $this->title = get_string('newhtmlblock', 'block_html'); } } function instance_allow_multiple() { return true; } function get_content() { global $CFG; require_once($CFG->libdir . '/filelib.php'); if ($this->content !== NULL) { return $this->content; } $filteropt = new stdClass; $filteropt->overflowdiv = true; if ($this->content_is_trusted()) { // fancy html allowed only on course, category and system blocks. $filteropt->noclean = true; } $this->content = new stdClass; $this->content->footer = ''; if (isset($this->config->text)) { // rewrite url $this->config->text = file_rewrite_pluginfile_urls($this->config->text, 'pluginfile.php', $this->context->id, 'block_html', 'content', NULL); // Default to FORMAT_HTML which is what will have been used before the // editor was properly implemented for the block. $format = FORMAT_HTML; // Check to see if the format has been properly set on the config if (isset($this->config->format)) { $format = $this->config->format; } $this->content->text = format_text($this->config->text, $format, $filteropt); } else { $this->content->text = ''; } unset($filteropt); // memory footprint return $this->content; } public function get_content_for_external($output) { $bc = new stdClass; $bc->title = null; $bc->content = ''; $bc->contenformat = FORMAT_MOODLE; $bc->footer = ''; $bc->files = []; if (!$this->hide_header()) { $bc->title = $this->title; } if (isset($this->config->text)) { $filteropt = new stdClass; if ($this->content_is_trusted()) { // Fancy html allowed only on course, category and system blocks. $filteropt->noclean = true; } $format = FORMAT_HTML; // Check to see if the format has been properly set on the config. if (isset($this->config->format)) { $format = $this->config->format; } [$bc->content, $bc->contentformat] = \core_external\util::format_text( $this->config->text, $format, $this->context, 'block_html', 'content', null, $filteropt ); $bc->files = external_util::get_area_files($this->context->id, 'block_html', 'content', false, false); } return $bc; } /** * Serialize and store config data */ function instance_config_save($data, $nolongerused = false) { global $DB; $config = clone($data); // Move embedded files into a proper filearea and adjust HTML links to match $config->text = file_save_draft_area_files($data->text['itemid'], $this->context->id, 'block_html', 'content', 0, array('subdirs'=>true), $data->text['text']); $config->format = $data->text['format']; parent::instance_config_save($config, $nolongerused); } function instance_delete() { global $DB; $fs = get_file_storage(); $fs->delete_area_files($this->context->id, 'block_html'); return true; } /** * Copy any block-specific data when copying to a new block instance. * @param int $fromid the id number of the block instance to copy from * @return boolean */ public function instance_copy($fromid) { $fromcontext = context_block::instance($fromid); $fs = get_file_storage(); // Do not use draft files hacks outside of forms. $files = $fs->get_area_files($fromcontext->id, 'block_html', 'content', 0, 'id ASC', false); foreach ($files as $file) { $filerecord = ['contextid' => $this->context->id]; $fs->create_file_from_storedfile($filerecord, $file); } return true; } function content_is_trusted() { global $SCRIPT; if (!$context = context::instance_by_id($this->instance->parentcontextid, IGNORE_MISSING)) { return false; } //find out if this block is on the profile page if ($context->contextlevel == CONTEXT_USER) { if ($SCRIPT === '/my/index.php') { // this is exception - page is completely private, nobody else may see content there // that is why we allow JS here return true; } else { // no JS on public personal pages, it would be a big security issue return false; } } return true; } /** * The block should only be dockable when the title of the block is not empty * and when parent allows docking. * * @return bool */ public function instance_can_be_docked() { return (!empty($this->config->title) && parent::instance_can_be_docked()); } /* * Add custom html attributes to aid with theming and styling * * @return array */ function html_attributes() { global $CFG; $attributes = parent::html_attributes(); if (!empty($CFG->block_html_allowcssclasses)) { if (!empty($this->config->classes)) { $attributes['class'] .= ' '.$this->config->classes; } } return $attributes; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { global $CFG; // Return all settings for all users since it is safe (no private keys, etc..). $instanceconfigs = !empty($this->config) ? $this->config : new stdClass(); $pluginconfigs = (object) ['allowcssclasses' => $CFG->block_html_allowcssclasses]; return (object) [ 'instance' => $instanceconfigs, 'plugin' => $pluginconfigs, ]; } } PK'9\l# html/lib.phpnu[. /** * Form for editing HTML block instances. * * @copyright 2010 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @package block_html * @category files * @param stdClass $course course object * @param stdClass $birecord_or_cm block instance record * @param context $context context object * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download * @param array $options additional options affecting the file serving * @return bool * @todo MDL-36050 improve capability check on stick blocks, so we can check user capability before sending images. */ function block_html_pluginfile($course, $birecord_or_cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG; require_once("{$CFG->dirroot}/user/lib.php"); if ($context->contextlevel != CONTEXT_BLOCK) { send_file_not_found(); } // If block is in course context, then check if user has capability to access course. if ($context->get_course_context(false)) { require_course_login($course); } else if ($CFG->forcelogin) { require_login(); } else { // Get parent context and see if user have proper permission. $parentcontext = $context->get_parent_context(); if ($parentcontext->contextlevel === CONTEXT_COURSECAT) { // Check if category is visible and user can view this category. if (!core_course_category::get($parentcontext->instanceid, IGNORE_MISSING)) { send_file_not_found(); } } else if ($parentcontext->contextlevel === CONTEXT_USER) { $user = core_user::get_user($parentcontext->instanceid, '*', MUST_EXIST); $extracaps = block_method_result('html', 'get_extra_capabilities'); if (!user_can_view_profile($user, null, $parentcontext) || !has_any_capability($extracaps, $context)) { send_file_not_found(); } } // At this point there is no way to check SYSTEM context, so ignoring it. } if ($filearea !== 'content') { send_file_not_found(); } $fs = get_file_storage(); $filename = array_pop($args); $filepath = $args ? '/'.implode('/', $args).'/' : '/'; if (!$file = $fs->get_file($context->id, 'block_html', 'content', 0, $filepath, $filename) or $file->is_directory()) { send_file_not_found(); } if ($parentcontext = context::instance_by_id($birecord_or_cm->parentcontextid, IGNORE_MISSING)) { if ($parentcontext->contextlevel == CONTEXT_USER) { // force download on all personal pages including /my/ //because we do not have reliable way to find out from where this is used $forcedownload = true; } } else { // weird, there should be parent context, better force dowload then $forcedownload = true; } // NOTE: it woudl be nice to have file revisions here, for now rely on standard file lifetime, // do not lower it because the files are dispalyed very often. \core\session\manager::write_close(); send_stored_file($file, null, 0, $forcedownload, $options); } /** * Perform global search replace such as when migrating site to new URL. * @param $search * @param $replace * @return void */ function block_html_global_db_replace($search, $replace) { global $DB; $instances = $DB->get_recordset('block_instances', array('blockname' => 'html')); foreach ($instances as $instance) { // TODO: intentionally hardcoded until MDL-26800 is fixed $config = unserialize_object(base64_decode($instance->configdata)); if (isset($config->text) and is_string($config->text)) { $config->text = str_replace($search, $replace, $config->text); $DB->update_record('block_instances', ['id' => $instance->id, 'configdata' => base64_encode(serialize($config)), 'timemodified' => time()]); } } $instances->close(); } /** * Given an array with a file path, it returns the itemid and the filepath for the defined filearea. * * @param string $filearea The filearea. * @param array $args The path (the part after the filearea and before the filename). * @return array The itemid and the filepath inside the $args path, for the defined filearea. */ function block_html_get_path_from_pluginfile(string $filearea, array $args): array { // This block never has an itemid (the number represents the revision but it's not stored in database). array_shift($args); // Get the filepath. if (empty($args)) { $filepath = '/'; } else { $filepath = '/' . implode('/', $args) . '/'; } return [ 'itemid' => 0, 'filepath' => $filepath, ]; } PK'9\pmhtml/version.phpnu[. /** * Version details * * @package block_html * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_html'; // Full name of the plugin (used for diagnostics) PK'9\CF;##!html/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_html. * * @package block_html * @copyright 2018 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_html\privacy; defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\request\userlist; use \core_privacy\local\request\approved_contextlist; use \core_privacy\local\request\approved_userlist; use \core_privacy\local\request\writer; use \core_privacy\local\request\helper; use \core_privacy\local\request\deletion_criteria; use \core_privacy\local\metadata\collection; /** * Privacy Subsystem implementation for block_html. * * @copyright 2018 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements // The block_html block stores user provided data. \core_privacy\local\metadata\provider, // This plugin is capable of determining which users have data within it. \core_privacy\local\request\core_userlist_provider, // The block_html block provides data directly to core. \core_privacy\local\request\plugin\provider { /** * Returns information about how block_html stores its data. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { $collection->link_subsystem('block', 'privacy:metadata:block'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist { // This block doesn't know who information is stored against unless it // is at the user context. $contextlist = new \core_privacy\local\request\contextlist(); $sql = "SELECT c.id FROM {block_instances} b INNER JOIN {context} c ON c.instanceid = b.id AND c.contextlevel = :contextblock INNER JOIN {context} bpc ON bpc.id = b.parentcontextid WHERE b.blockname = 'html' AND bpc.contextlevel = :contextuser AND bpc.instanceid = :userid"; $params = [ 'contextblock' => CONTEXT_BLOCK, 'contextuser' => CONTEXT_USER, 'userid' => $userid, ]; $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { // This block doesn't know who information is stored against unless it // is at the user context. $context = $userlist->get_context(); if (!$context instanceof \context_block) { return; } $sql = "SELECT bpc.instanceid AS userid FROM {block_instances} bi JOIN {context} bpc ON bpc.id = bi.parentcontextid WHERE bi.blockname = 'html' AND bpc.contextlevel = :contextuser AND bi.id = :blockinstanceid"; $params = [ 'contextuser' => CONTEXT_USER, 'blockinstanceid' => $context->instanceid ]; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { global $DB; $user = $contextlist->get_user(); list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); $sql = "SELECT c.id AS contextid, bi.* FROM {context} c INNER JOIN {block_instances} bi ON bi.id = c.instanceid AND c.contextlevel = :contextlevel WHERE bi.blockname = 'html' AND( c.id {$contextsql} ) "; $params = [ 'contextlevel' => CONTEXT_BLOCK, ]; $params += $contextparams; $instances = $DB->get_recordset_sql($sql, $params); foreach ($instances as $instance) { $context = \context_block::instance($instance->id); $block = block_instance('html', $instance); if (empty($block->config)) { // Skip this block. It has not been configured. continue; } $html = writer::with_context($context) ->rewrite_pluginfile_urls([], 'block_html', 'content', null, $block->config->text); // Default to FORMAT_HTML which is what will have been used before the // editor was properly implemented for the block. $format = isset($block->config->format) ? $block->config->format : FORMAT_HTML; $filteropt = (object) [ 'overflowdiv' => true, 'noclean' => true, ]; $html = format_text($html, $format, $filteropt); $data = helper::get_context_data($context, $user); helper::export_context_files($context, $user); $data->title = $block->config->title; $data->content = $html; writer::with_context($context)->export_data([], $data); } $instances->close(); } /** * Delete all data for all users in the specified context. * * @param \context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(\context $context) { if (!$context instanceof \context_block) { return; } // The only way to delete data for the html block is to delete the block instance itself. if ($blockinstance = static::get_instance_from_context($context)) { blocks_delete_instance($blockinstance); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { $context = $userlist->get_context(); if ($context instanceof \context_block && ($blockinstance = static::get_instance_from_context($context))) { blocks_delete_instance($blockinstance); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { // The only way to delete data for the html block is to delete the block instance itself. foreach ($contextlist as $context) { if (!$context instanceof \context_block) { continue; } if ($blockinstance = static::get_instance_from_context($context)) { blocks_delete_instance($blockinstance); } } } /** * Get the block instance record for the specified context. * * @param \context_block $context The context to fetch * @return \stdClass */ protected static function get_instance_from_context(\context_block $context) { global $DB; return $DB->get_record('block_instances', ['id' => $context->instanceid, 'blockname' => 'html']); } } PK'9\̉  html/classes/search/content.phpnu[. /** * Search area for block_html blocks * * @package block_html * @copyright 2017 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_html\search; use core_search\moodle_recordset; defined('MOODLE_INTERNAL') || die(); /** * Search area for block_html blocks * * @package block_html * @copyright 2017 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class content extends \core_search\base_block { public function get_document($record, $options = array()) { // Create empty document. $doc = \core_search\document_factory::instance($record->id, $this->componentname, $this->areaname); // Get stdclass object with data from DB. $data = unserialize_object(base64_decode($record->configdata)); // Get content. $content = content_to_text($data->text, $data->format); $doc->set('content', $content); if (isset($data->title)) { // If there is a title, use it as title. $doc->set('title', content_to_text($data->title, false)); } else { // If there is no title, use the content text again. $doc->set('title', shorten_text($content)); } // Set standard fields. $doc->set('contextid', $record->contextid); $doc->set('type', \core_search\manager::TYPE_TEXT); $doc->set('courseid', $record->courseid); $doc->set('modified', $record->timemodified); $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID); // Mark document new if appropriate. if (isset($options['lastindexedtime']) && ($options['lastindexedtime'] < $record->timecreated)) { // If the document was created after the last index time, it must be new. $doc->set_is_new(true); } return $doc; } public function uses_file_indexing() { return true; } public function attach_files($document) { $fs = get_file_storage(); $context = \context::instance_by_id($document->get('contextid')); $files = $fs->get_area_files($context->id, 'block_html', 'content', false, 'itemid, filepath, filename', false); foreach ($files as $file) { $document->add_stored_file($file); } } } PK'9\i#i@&=&=$html/tests/privacy/provider_test.phpnu[. /** * Unit tests for the block_html implementation of the privacy API. * * @package block_html * @category test * @copyright 2018 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_html\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\local\request\writer; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\approved_userlist; use block_html\privacy\provider; /** * Unit tests for the block_html implementation of the privacy API. * * @copyright 2018 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends \core_privacy\tests\provider_testcase { /** * Get the list of standard format options for comparison. * * @return \stdClass */ protected function get_format_options() { return (object) [ 'overflowdiv' => true, 'noclean' => true, ]; } /** * Creates an HTML block on a user. * * @param string $title * @param string $body * @param string $format * @return \block_instance */ protected function create_user_block($title, $body, $format) { global $USER; $configdata = (object) [ 'title' => $title, 'text' => [ 'itemid' => 19, 'text' => $body, 'format' => $format, ], ]; $this->create_block($this->construct_user_page($USER)); $block = $this->get_last_block_on_page($this->construct_user_page($USER)); $block = block_instance('html', $block->instance); $block->instance_config_save((object) $configdata); return $block; } /** * Creates an HTML block on a course. * * @param \stdClass $course * @param string $title * @param string $body * @param string $format * @return \block_instance */ protected function create_course_block($course, $title, $body, $format) { global $USER; $configdata = (object) [ 'title' => $title, 'text' => [ 'itemid' => 19, 'text' => $body, 'format' => $format, ], ]; $this->create_block($this->construct_course_page($course)); $block = $this->get_last_block_on_page($this->construct_course_page($course)); $block = block_instance('html', $block->instance); $block->instance_config_save((object) $configdata); return $block; } /** * Creates an HTML block on a page. * * @param \page $page Page */ protected function create_block($page) { $page->blocks->add_block_at_end_of_default_region('html'); } /** * Get the last block on the page. * * @param \page $page Page * @return \block_html Block instance object */ protected function get_last_block_on_page($page) { $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region()); $block = end($blocks); return $block; } /** * Constructs a Page object for the User Dashboard. * * @param \stdClass $user User to create Dashboard for. * @return \moodle_page */ protected function construct_user_page(\stdClass $user) { $page = new \moodle_page(); $page->set_context(\context_user::instance($user->id)); $page->set_pagelayout('mydashboard'); $page->set_pagetype('my-index'); $page->blocks->load_blocks(); return $page; } /** * Constructs a Page object for the User Dashboard. * * @param \stdClass $course Course to create Dashboard for. * @return \moodle_page */ protected function construct_course_page(\stdClass $course) { $page = new \moodle_page(); $page->set_context(\context_course::instance($course->id)); $page->set_pagelayout('standard'); $page->set_pagetype('course-view'); $page->set_course($course); $page->blocks->load_blocks(); return $page; } /** * Test that a block on the dashboard is exported. */ public function test_user_block(): void { $this->resetAfterTest(); $title = 'Example title'; $content = 'Example content'; $format = FORMAT_PLAIN; // Test setup. $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $block = $this->create_user_block($title, $content, $format); $context = \context_block::instance($block->instance->id); // Get the contexts. $contextlist = provider::get_contexts_for_userid($user->id); // Only the user context should be returned. $this->assertCount(1, $contextlist); $this->assertEquals($context, $contextlist->current()); // Export the data. $this->export_context_data_for_user($user->id, $context, 'block_html'); /** @var \core_privacy\tests\request\content_writer $writer */ $writer = \core_privacy\local\request\writer::with_context($context); $this->assertTrue($writer->has_any_data()); // Check the data. $data = $writer->get_data([]); $this->assertInstanceOf('stdClass', $data); $this->assertEquals($title, $data->title); $this->assertEquals(format_text($content, $format, $this->get_format_options()), $data->content); // Delete the context. provider::delete_data_for_all_users_in_context($context); // Re-fetch the contexts - it should no longer be returned. $contextlist = provider::get_contexts_for_userid($user->id); $this->assertCount(0, $contextlist); } /** * Test that a block on the dashboard which is not configured is _not_ exported. */ public function test_user_block_unconfigured(): void { global $DB; $this->resetAfterTest(); $title = 'Example title'; $content = 'Example content'; $format = FORMAT_PLAIN; // Test setup. $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $block = $this->create_user_block($title, $content, $format); $block->instance->configdata = ''; $DB->update_record('block_instances', $block->instance); $block = block_instance('html', $block->instance); $context = \context_block::instance($block->instance->id); // Get the contexts. $contextlist = provider::get_contexts_for_userid($user->id); // Only the user context should be returned. $this->assertCount(1, $contextlist); $this->assertEquals($context, $contextlist->current()); // Export the data. $this->export_context_data_for_user($user->id, $context, 'block_html'); /** @var \core_privacy\tests\request\content_writer $writer */ $writer = \core_privacy\local\request\writer::with_context($context); $this->assertFalse($writer->has_any_data()); } /** * Test that a block on the dashboard is exported. */ public function test_user_multiple_blocks_exported(): void { $this->resetAfterTest(); $title = 'Example title'; $content = 'Example content'; $format = FORMAT_PLAIN; // Test setup. $blocks = []; $contexts = []; $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $block = $this->create_user_block($title, $content, $format); $context = \context_block::instance($block->instance->id); $contexts[$context->id] = $context; $block = $this->create_user_block($title, $content, $format); $context = \context_block::instance($block->instance->id); $contexts[$context->id] = $context; // Get the contexts. $contextlist = provider::get_contexts_for_userid($user->id); // There are now two blocks on the user context. $this->assertCount(2, $contextlist); foreach ($contextlist as $context) { $this->assertTrue(isset($contexts[$context->id])); } // Turn them into an approved_contextlist. $approvedlist = new approved_contextlist($user, 'block_html', $contextlist->get_contextids()); // Delete using delete_data_for_user. provider::delete_data_for_user($approvedlist); // Re-fetch the contexts - it should no longer be returned. $contextlist = provider::get_contexts_for_userid($user->id); $this->assertCount(0, $contextlist); } /** * Test that a block on the dashboard is not exported. */ public function test_course_blocks_not_exported(): void { $this->resetAfterTest(); $title = 'Example title'; $content = 'Example content'; $format = FORMAT_PLAIN; // Test setup. $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $this->setUser($user); $this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher'); $block = $this->create_course_block($course, $title, $content, $format); $context = \context_block::instance($block->instance->id); // Get the contexts. $contextlist = provider::get_contexts_for_userid($user->id); // No blocks should be returned. $this->assertCount(0, $contextlist); } /** * Test that a block on the dashboard is exported. */ public function test_mixed_multiple_blocks_exported(): void { $this->resetAfterTest(); $title = 'Example title'; $content = 'Example content'; $format = FORMAT_PLAIN; // Test setup. $contexts = []; $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $this->setUser($user); $this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher'); $block = $this->create_course_block($course, $title, $content, $format); $context = \context_block::instance($block->instance->id); $block = $this->create_user_block($title, $content, $format); $context = \context_block::instance($block->instance->id); $contexts[$context->id] = $context; $block = $this->create_user_block($title, $content, $format); $context = \context_block::instance($block->instance->id); $contexts[$context->id] = $context; // Get the contexts. $contextlist = provider::get_contexts_for_userid($user->id); // There are now two blocks on the user context. $this->assertCount(2, $contextlist); foreach ($contextlist as $context) { $this->assertTrue(isset($contexts[$context->id])); } } /** * Test that only users with a user context HTML block are fetched. */ public function test_get_users_in_context(): void { $this->resetAfterTest(); $component = 'block_html'; $title = 'Block title'; $content = 'Block content'; $blockformat = FORMAT_PLAIN; // Create a user with a user context HTML block. $user1 = $this->getDataGenerator()->create_user(); $this->setUser($user1); $userblock = $this->create_user_block($title, $content, $blockformat); $usercontext = \context_block::instance($userblock->instance->id); // Create a user with a course context HTML block. $user2 = $this->getDataGenerator()->create_user(); $this->setUser($user2); $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'editingteacher'); $courseblock = $this->create_course_block($course, $title, $content, $blockformat); $coursecontext = \context_block::instance($courseblock->instance->id); // Ensure only the user with a user context HTML block is returned. $userlist = new \core_privacy\local\request\userlist($usercontext, $component); \block_html\privacy\provider::get_users_in_context($userlist); $this->assertCount(1, $userlist); $expected = [$user1->id]; $actual = $userlist->get_userids(); $this->assertEquals($expected, $actual); // Ensure passing the course context returns no users. $userlist = new \core_privacy\local\request\userlist($coursecontext, $component); \mod_forum\privacy\provider::get_users_in_context($userlist); $this->assertEmpty($userlist); } /** * Test that data for users in approved userlist is deleted. */ public function test_delete_data_for_users(): void { $this->resetAfterTest(); $component = 'block_html'; $title = 'Block title'; $content = 'Block content'; $blockformat = FORMAT_PLAIN; // Create 2 user swith a user context HTML blocks. $user1 = $this->getDataGenerator()->create_user(); $this->setUser($user1); $block1 = $this->create_user_block($title, $content, $blockformat); $context1 = \context_block::instance($block1->instance->id); $user2 = $this->getDataGenerator()->create_user(); $this->setUser($user2); $block2 = $this->create_user_block($title, $content, $blockformat); $context2 = \context_block::instance($block2->instance->id); // Create and populate the userlists. $userlist1 = new \core_privacy\local\request\userlist($context1, $component); \block_html\privacy\provider::get_users_in_context($userlist1); $userlist2 = new \core_privacy\local\request\userlist($context2, $component); \block_html\privacy\provider::get_users_in_context($userlist2); // Ensure both members are included. $this->assertCount(1, $userlist1); $this->assertCount(1, $userlist2); // Convert $userlist1 into an approved_contextlist. $approvedlist = new approved_userlist($context1, 'block_html', $userlist1->get_userids()); // Delete using delete_data_for_user. provider::delete_data_for_users($approvedlist); // Re-fetch users in the contexts - only the first one should now be empty. $userlist1 = new \core_privacy\local\request\userlist($context1, $component); \block_html\privacy\provider::get_users_in_context($userlist1); $this->assertCount(0, $userlist1); $userlist2 = new \core_privacy\local\request\userlist($context2, $component); \block_html\privacy\provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); } } PK'9\z''html/tests/block_html_test.phpnu[. namespace block_html; /** * Unit test for block_html class. * * @package block_html * @copyright 2022 Open LMS (https://www.openlms.net/) * @author Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @coversDefaultClass \block_html */ final class block_html_test extends \advanced_testcase { /** * Tests instance files copying. * @covers ::instance_copy */ public function test_instance_copy(): void { global $USER; $this->resetAfterTest(); $this->setAdminUser(); $fs = get_file_storage(); $course = $this->getDataGenerator()->create_course(); $block1 = $this->create_block($course); $itemid = file_get_unused_draft_itemid(); $fs = get_file_storage(); $usercontext = \context_user::instance($USER->id); $fs->create_file_from_string(['component' => 'user', 'filearea' => 'draft', 'contextid' => $usercontext->id, 'itemid' => $itemid, 'filepath' => '/', 'filename' => 'file.txt'], 'File content'); $data = (object)['title' => 'Block title', 'text' => ['text' => 'Block text', 'itemid' => $itemid, 'format' => FORMAT_HTML]]; $block1->instance_config_save($data); $this->assertTrue($fs->file_exists($block1->context->id, 'block_html', 'content', 0, '/', 'file.txt')); $block2 = $this->create_block($course); $this->assertFalse($fs->file_exists($block2->context->id, 'block_html', 'content', 0, '/', 'file.txt')); $this->setUser(null); $block2->instance_copy($block1->instance->id); $this->assertTrue($fs->file_exists($block2->context->id, 'block_html', 'content', 0, '/', 'file.txt')); } /** * Constructs a page object for the test course. * * @param \stdClass $course Moodle course object * @return \moodle_page Page object representing course view */ protected static function construct_page($course): \moodle_page { $context = \context_course::instance($course->id); $page = new \moodle_page(); $page->set_context($context); $page->set_course($course); $page->set_pagelayout('standard'); $page->set_pagetype('course-view'); $page->blocks->load_blocks(); return $page; } /** * Creates an HTML block on a course. * * @param \stdClass $course Course object * @return \block_html Block instance object */ protected function create_block($course): \block_html { $page = self::construct_page($course); $page->blocks->add_block_at_end_of_default_region('html'); // Load the block. $page = self::construct_page($course); $page->blocks->load_blocks(); $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region()); $block = end($blocks); return $block; } } PK'9\/html/tests/behat/configuring_html_block.featurenu[@block @block_html @core_block Feature: Adding and configuring Text blocks In order to have custom blocks on a page As admin I need to be able to create, configure and change Text blocks @javascript Scenario: Configuring the Text block with Javascript on Given I log in as "admin" And I am on site homepage When I turn editing mode on And I add the "Text" block to the default region with: | Text block title | | | Content | Static text without a header | Then I should not see "(new text block)" And I configure the "block_html" block And I set the field "Text block title" to "The Text block header" And I set the field "Content" to "Static text with a header" And I press "Save changes" And "block_html" "block" should exist And "The Text block header" "block" should exist And I should see "Static text with a header" in the "The Text block header" "block" Scenario: Configuring the Text block with Javascript off Given I log in as "admin" And I am on site homepage When I turn editing mode on And I add the "Text" block And I configure the "(new text block)" block And I set the field "Content" to "Static text without a header" And I press "Save changes" Then I should not see "(new text block)" And I configure the "block_html" block And I set the field "Text block title" to "The Text block header" And I set the field "Content" to "Static text with a header" And I press "Save changes" And "block_html" "block" should exist And "The Text block header" "block" should exist And I should see "Static text with a header" in the "The Text block header" "block" PK'9\]%html/tests/behat/course_block.featurenu[@block @block_html Feature: Text blocks in a course In order to have one or multiple Text blocks in a course As a teacher I need to be able to create and change such blocks Scenario: Adding Text block in a course Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Terry1 | Teacher1 | teacher@example.com | | student1 | Sam1 | Student1 | student1@example.com | And the following "courses" exist: | fullname | shortname | | Course 1 | C1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Text" block And I configure the "(new text block)" block And I set the field "Content" to "First block content" And I set the field "Text block title" to "First block header" And I press "Save changes" And I add the "Text" block And I configure the "(new text block)" block And I set the field "Content" to "Second block content" And I set the field "Text block title" to "Second block header" And I press "Save changes" And I log out And I log in as "student1" And I am on "Course 1" course homepage And I should see "First block content" in the "First block header" "block" And I should see "Second block content" in the "Second block header" "block" PK'9\vr  +html/tests/behat/multiple_instances.featurenu[@block @block_html Feature: Adding and configuring multiple Text blocks In order to have one or multiple Text blocks on a page As admin I need to be able to create, configure and change Text blocks Background: Given I log in as "admin" And I am on site homepage When I turn editing mode on And I add the "Text" block Scenario: Other users can not see Text block that has not been configured Then "(new text block)" "block" should exist And I log out And "(new text block)" "block" should not exist And "block_html" "block" should not exist Scenario: Other users can see Text block that has been configured even when it has no header And I configure the "(new text block)" block And I set the field "Content" to "Static text without a header" And I press "Save changes" Then I should not see "(new text block)" And I log out And I am on homepage And "block_html" "block" should exist And I should see "Static text without a header" in the "block_html" "block" And I should not see "(new text block)" Scenario: Adding multiple instances of Text block on a page And I configure the "block_html" block And I set the field "Text block title" to "The Text block header" And I set the field "Content" to "Static text with a header" And I press "Save changes" And I add the "Text" block And I configure the "(new text block)" block And I set the field "Text block title" to "The second Text block header" And I set the field "Content" to "Second block contents" And I press "Save changes" And I log out Then I should see "Static text with a header" in the "The Text block header" "block" And I should see "Second block contents" in the "The second Text block header" "block" PK'9\d"html/tests/search_content_test.phpnu[. /** * Unit test for search indexing. * * @package block_html * @copyright 2017 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_html; defined('MOODLE_INTERNAL') || die(); /** * Unit test for search indexing. * * @package block_html * @copyright 2017 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class search_content_test extends \advanced_testcase { /** * Creates an HTML block on a course. * * @param \stdClass $course Course object * @return \block_html Block instance object */ protected function create_block($course) { $page = self::construct_page($course); $page->blocks->add_block_at_end_of_default_region('html'); // Load the block. $page = self::construct_page($course); $page->blocks->load_blocks(); $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region()); $block = end($blocks); return $block; } /** * Constructs a page object for the test course. * * @param \stdClass $course Moodle course object * @return \moodle_page Page object representing course view */ protected static function construct_page($course) { $context = \context_course::instance($course->id); $page = new \moodle_page(); $page->set_context($context); $page->set_course($course); $page->set_pagelayout('standard'); $page->set_pagetype('course-view'); $page->blocks->load_blocks(); return $page; } /** * Tests all functionality in the search area. */ public function test_search_area(): void { global $CFG, $USER, $DB; require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php'); $this->resetAfterTest(); $this->setAdminUser(); // Create course and add HTML block. $generator = $this->getDataGenerator(); $course = $generator->create_course(); $before = time(); $block = $this->create_block($course); // Change block settings to add some text and a file. $itemid = file_get_unused_draft_itemid(); $fs = get_file_storage(); $usercontext = \context_user::instance($USER->id); $fs->create_file_from_string(['component' => 'user', 'filearea' => 'draft', 'contextid' => $usercontext->id, 'itemid' => $itemid, 'filepath' => '/', 'filename' => 'file.txt'], 'File content'); $data = (object)['title' => 'Block title', 'text' => ['text' => 'Block text', 'itemid' => $itemid, 'format' => FORMAT_HTML]]; $block->instance_config_save($data); $after = time(); // Set up fake search engine so we can create documents. \testable_core_search::instance(); // Do indexing query. $area = new \block_html\search\content(); $this->assertEquals('html', $area->get_block_name()); $rs = $area->get_recordset_by_timestamp(); $count = 0; foreach ($rs as $record) { $count++; $this->assertEquals($course->id, $record->courseid); // Check context is correct. $blockcontext = \context::instance_by_id($record->contextid); $this->assertInstanceOf('\context_block', $blockcontext); $coursecontext = $blockcontext->get_parent_context(); $this->assertEquals($course->id, $coursecontext->instanceid); // Check created and modified times are correct. $this->assertTrue($record->timecreated >= $before && $record->timecreated <= $after); $this->assertTrue($record->timemodified >= $before && $record->timemodified <= $after); // Get config data. $data = unserialize(base64_decode($record->configdata)); $this->assertEquals('Block title', $data->title); $this->assertEquals('Block text', $data->text); $this->assertEquals(FORMAT_HTML, $data->format); // Check the get_document function 'new' flag. $doc = $area->get_document($record, ['lastindexedtime' => 1]); $this->assertTrue($doc->get_is_new()); $doc = $area->get_document($record, ['lastindexedtime' => time() + 1]); $this->assertFalse($doc->get_is_new()); // Check the attach_files function results in correct list of associated files. $this->assertCount(0, $doc->get_files()); $area->attach_files($doc); $files = $doc->get_files(); // The directory entry should NOT be provided for indexing. $this->assertCount(1, $files); foreach ($files as $file) { $this->assertEquals('file.txt', $file->get_filename()); $this->assertEquals('File content', $file->get_content()); } // Check the document fields are all as expected. $this->assertEquals('Block title', $doc->get('title')); $this->assertEquals('Block text', $doc->get('content')); $this->assertEquals($blockcontext->id, $doc->get('contextid')); $this->assertEquals(\core_search\manager::TYPE_TEXT, $doc->get('type')); $this->assertEquals($course->id, $doc->get('courseid')); $this->assertEquals($record->timemodified, $doc->get('modified')); $this->assertEquals(\core_search\manager::NO_OWNER_ID, $doc->get('owneruserid')); // Also check getting the doc url and context url. $url = new \moodle_url('/course/view.php', ['id' => $course->id], 'inst' . $record->id); $this->assertTrue($url->compare($area->get_doc_url($doc))); $this->assertTrue($url->compare($area->get_context_url($doc))); } $rs->close(); // Should only be one HTML block systemwide. $this->assertEquals(1, $count); // If we run the query starting from 1 second after now, there should be no results. $rs = $area->get_recordset_by_timestamp($after + 1); $count = 0; foreach ($rs as $record) { $count++; } $rs->close(); $this->assertEquals(0, $count); // Create another block, but this time leave it empty (no data set). Hack the time though. $block = $this->create_block($course); $DB->set_field('block_instances', 'timemodified', $after + 10, ['id' => $block->instance->id]); $rs = $area->get_recordset_by_timestamp($after + 10); $count = 0; foreach ($rs as $record) { // Because there is no configdata we don't index it. $count++; } $rs->close(); $this->assertEquals(0, $count); } } PK'9\UW\html/settings.phpnu[. /** * Settings for the HTML block * * @copyright 2012 Aaron Barnes * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @package block_html */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { $settings->add(new admin_setting_configcheckbox('block_html_allowcssclasses', get_string('allowadditionalcssclasses', 'block_html'), get_string('configallowadditionalcssclasses', 'block_html'), 0)); } PK'9\ښ  mentees/db/access.phpnu[. /** * Mentees block caps. * * @package block_mentees * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/mentees:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/mentees:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\Gvv!mentees/lang/en/block_mentees.phpnu[. /** * Strings for component 'block_mentees', language 'en', branch 'MOODLE_20_STABLE' * * @package block_mentees * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['configtitle'] = 'Mentees block title'; $string['configtitleblankhides'] = 'Mentees block title (no title if blank)'; $string['mentees:addinstance'] = 'Add a new mentees block'; $string['mentees:myaddinstance'] = 'Add a new mentees block to Dashboard'; $string['newmenteesblock'] = '(new Mentees block)'; $string['pluginname'] = 'Mentees'; $string['privacy:metadata'] = 'The Mentees block only shows data stored in other locations.'; PK'9\J߲mentees/edit_form.phpnu[. /** * Form for editing Mentees block instances. * * @package block_mentees * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Form for editing Mentees block instances. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_mentees_edit_form extends block_edit_form { protected function specific_definition($mform) { // Fields for editing HTML block title and contents. $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $mform->addElement('text', 'config_title', get_string('configtitleblankhides', 'block_mentees')); $mform->setType('config_title', PARAM_TEXT); } }PK'9\}jmentees/version.phpnu[. /** * Version details * * @package block_mentees * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_mentees'; // Full name of the plugin (used for diagnostics) PK'9\)f$mentees/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_mentees. * * @package block_mentees * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_mentees\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_mentees implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\hg5mentees/tests/behat/configuring_mentees_block.featurenu[@block @block_mentees @core_block Feature: Adding and configuring Mentees blocks In order to have a Mentees blocks on a page As admin I need to be able to insert and configure a Mentees blocks @javascript Scenario: Configuring the Mentees block with Javascript on Given I log in as "admin" And I am on site homepage When I turn editing mode on And I add the "Mentees" block And I configure the "(new Mentees block)" block Then I should see "Mentees block title (no title if blank)" And I set the field "Mentees block title (no title if blank)" to "The Mentees block header" And I press "Save changes" And "block_mentees" "block" should exist Then "The Mentees block header" "block" should exist PK'9\~mentees/block_mentees.phpnu[. use core\output\html_writer; use core\user; /** * Mentees block. * * @package block_mentees * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_mentees extends block_base { function init() { $this->title = get_string('pluginname', 'block_mentees'); } function applicable_formats() { return array('all' => true, 'tag' => false); } function specialization() { $this->title = isset($this->config->title) ? $this->config->title : get_string('newmenteesblock', 'block_mentees'); } function instance_allow_multiple() { return true; } function get_content() { global $USER, $DB; if ($this->content !== NULL) { return $this->content; } $this->content = new stdClass(); // get all the mentees, i.e. users you have a direct assignment to $userfieldsapi = \core_user\fields::for_name(); $userfieldssql = $userfieldsapi->get_sql('u', false, '', '', false); [$usersort] = users_order_by_sql('u', null, $this->context, $userfieldssql->mappings); if ($users = $DB->get_records_sql("SELECT u.id, $userfieldssql->selects FROM {role_assignments} ra, {context} c, {user} u WHERE ra.userid = ? AND ra.contextid = c.id AND c.instanceid = u.id AND c.contextlevel = ? ORDER BY $usersort", [$USER->id, CONTEXT_USER])) { $this->content->text = '
                '; foreach ($users as $user) { $userprofileurl = user::get_profile_url($user); $userfullname = user::get_fullname($user, $this->context); $this->content->text .= '
              • ' . html_writer::link($userprofileurl, $userfullname) . '
              • '; } $this->content->text .= '
              '; } $this->content->footer = ''; return $this->content; } /** * Returns true if the block can be docked. * The mentees block can only be docked if it has a non-empty title. * @return bool */ public function instance_can_be_docked() { return parent::instance_can_be_docked() && isset($this->config->title) && !empty($this->config->title); } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = !empty($this->config) ? $this->config : new stdClass(); return (object) [ 'instance' => $configs, 'plugin' => new stdClass(), ]; } } PK'9\¥=amd/src/edit.jsnu[// 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 . /** * Javascript module for editing blocks * * @module core_block/edit * @copyright 2022 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import ModalForm from 'core_form/modalform'; const SELECTORS = { EDITBLOCK: '[data-action="editblock"][data-blockid][data-blockform]', }; /** * Initialize module * @param {String} pagehash */ export const init = (pagehash) => { document.addEventListener('click', e => { const target = e.target.closest(SELECTORS.EDITBLOCK); if (!target || !target.getAttribute('data-blockform')) { return; } e.preventDefault(); const modalForm = new ModalForm({ modalConfig: { title: target.getAttribute('data-header'), }, args: {blockid: target.getAttribute('data-blockid'), pagehash}, formClass: target.getAttribute('data-blockform'), returnFocus: target, }); // Reload the page when the form is submitted, there is no possibility // currently to request contents update of just one block on the page. modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => { location.reload(); }); modalForm.show(); }); }; PK'9\dP P amd/src/events.jsnu[// 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 . /** * Javascript events for the `core_block` subsystem. * * @module core_block/events * @copyright 2021 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 4.0 * * @example Example of listening to a block event. * import {eventTypes as blockEventTypes} from 'core_block/events'; * * document.addEventListener(blockEventTypes.blockContentUpdated, e => { * window.console.log(e.target); // The HTMLElement relating to the block whose content was updated. * window.console.log(e.detail.instanceId); // The instanceId of the block that was updated. * }); */ import {dispatchEvent} from 'core/event_dispatcher'; /** * Events for `core_block`. * * @constant * @property {String} blockContentUpdated See {@link event:blockContentUpdated} */ export const eventTypes = { /** * An event triggered when the content of a block has changed. * * @event blockContentUpdated * @type {CustomEvent} * @property {HTMLElement} target The block element that was updated * @property {object} detail * @property {number} detail.instanceId The block instance id */ blockContentUpdated: 'core_block/contentUpdated', }; /** * Trigger an event to indicate that the content of a block was updated. * * @method notifyBlockContentUpdated * @param {HTMLElement} element The HTMLElement containing the updated block. * @returns {CustomEvent} * @fires blockContentUpdated */ export const notifyBlockContentUpdated = element => dispatchEvent( eventTypes.blockContentUpdated, { instanceId: element.dataset.instanceId, }, element ); PK'9\xtMamd/src/add_modal.jsnu[// 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 . /** * Show an add block modal instead of doing it on a separate page. * * @module core_block/add_modal * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import Templates from 'core/templates'; import {getString} from 'core/str'; import Ajax from 'core/ajax'; import ModalForm from "core_form/modalform"; import CancelModal from 'core/modal_cancel'; const SELECTORS = { ADD_BLOCK: '[data-key="addblock"]', SHOW_BLOCK_FORM: '[data-action="showaddblockform"][data-blockname][data-blockform]' }; // Ensure we only add our listeners once. let listenerEventsRegistered = false; /** * Register related event listeners. * * @method registerListenerEvents * @param {String|null} addBlockUrl The add block URL * @param {String} pagehash */ const registerListenerEvents = (addBlockUrl, pagehash) => { let addBlockModal = null; document.addEventListener('click', e => { const showAddBlockForm = e.target.closest(SELECTORS.SHOW_BLOCK_FORM); if (showAddBlockForm) { e.preventDefault(); const modalForm = new ModalForm({ modalConfig: { title: getString('addblock', 'core_block', showAddBlockForm.getAttribute('data-blocktitle')), }, args: {blockname: showAddBlockForm.getAttribute('data-blockname'), pagehash, blockregion: showAddBlockForm.getAttribute('data-blockregion')}, formClass: showAddBlockForm.getAttribute('data-blockform'), returnFocus: showAddBlockForm, }); modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => { addBlockModal.destroy(); window.location.reload(); }); modalForm.show(); } const addBlock = e.target.closest(SELECTORS.ADD_BLOCK); if (addBlock) { e.preventDefault(); let addBlockModalUrl = addBlockUrl ?? addBlock.dataset.url; buildAddBlockModal() .then(modal => { addBlockModal = modal; const modalBody = renderBlocks(addBlockModalUrl, pagehash, addBlock.getAttribute('data-blockregion')); modal.setBody(modalBody); modal.show(); return modalBody; }) .catch(() => { addBlockModal.destroy(); }); } }); }; /** * Method that creates the 'add block' modal. * * @method buildAddBlockModal * @returns {Promise} The modal promise (modal's body will be rendered later). */ const buildAddBlockModal = () => CancelModal.create({ title: getString('addblock'), }); /** * Method that renders the list of available blocks. * * @method renderBlocks * @param {String} addBlockUrl The add block URL * @param {String} pagehash * @param {String} region * @return {Promise} */ const renderBlocks = async(addBlockUrl, pagehash, region) => { // Fetch all addable blocks in the given page. const blocks = await getAddableBlocks(pagehash); return Templates.render('core/add_block_body', { blocks: blocks, url: addBlockUrl, blockregion: region, pagehash }); }; /** * Method that fetches all addable blocks in a given page. * * @method getAddableBlocks * @param {String} pagehash * @return {Promise} */ const getAddableBlocks = async(pagehash) => { const request = { methodname: 'core_block_fetch_addable_blocks', args: { pagecontextid: 0, pagetype: '', pagelayout: '', subpage: '', pagehash: pagehash, }, }; return Ajax.call([request])[0]; }; /** * Set up the actions. * * @method init * @param {String} addBlockUrl The add block URL * @param {String} pagehash */ export const init = (addBlockUrl = null, pagehash = '') => { if (!listenerEventsRegistered) { registerListenerEvents(addBlockUrl, pagehash); listenerEventsRegistered = true; } }; PK'9\?IIamd/build/edit.min.jsnu[define("core_block/edit",["exports","core_form/modalform"],(function(_exports,_modalform){var obj; /** * Javascript module for editing blocks * * @module core_block/edit * @copyright 2022 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modalform=(obj=_modalform)&&obj.__esModule?obj:{default:obj};const SELECTORS_EDITBLOCK='[data-action="editblock"][data-blockid][data-blockform]';_exports.init=pagehash=>{document.addEventListener("click",(e=>{const target=e.target.closest(SELECTORS_EDITBLOCK);if(!target||!target.getAttribute("data-blockform"))return;e.preventDefault();const modalForm=new _modalform.default({modalConfig:{title:target.getAttribute("data-header")},args:{blockid:target.getAttribute("data-blockid"),pagehash:pagehash},formClass:target.getAttribute("data-blockform"),returnFocus:target});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(()=>{location.reload()})),modalForm.show()}))}})); //# sourceMappingURL=edit.min.js.mapPK'9\TS amd/build/edit.min.js.mapnu[{"version":3,"file":"edit.min.js","sources":["../src/edit.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript module for editing blocks\n *\n * @module core_block/edit\n * @copyright 2022 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalForm from 'core_form/modalform';\n\nconst SELECTORS = {\n EDITBLOCK: '[data-action=\"editblock\"][data-blockid][data-blockform]',\n};\n\n/**\n * Initialize module\n * @param {String} pagehash\n */\nexport const init = (pagehash) => {\n document.addEventListener('click', e => {\n const target = e.target.closest(SELECTORS.EDITBLOCK);\n if (!target || !target.getAttribute('data-blockform')) {\n return;\n }\n e.preventDefault();\n\n const modalForm = new ModalForm({\n modalConfig: {\n title: target.getAttribute('data-header'),\n },\n args: {blockid: target.getAttribute('data-blockid'), pagehash},\n formClass: target.getAttribute('data-blockform'),\n returnFocus: target,\n });\n\n // Reload the page when the form is submitted, there is no possibility\n // currently to request contents update of just one block on the page.\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => {\n location.reload();\n });\n\n modalForm.show();\n });\n};\n"],"names":["SELECTORS","pagehash","document","addEventListener","e","target","closest","getAttribute","preventDefault","modalForm","ModalForm","modalConfig","title","args","blockid","formClass","returnFocus","events","FORM_SUBMITTED","location","reload","show"],"mappings":";;;;;;;sJAyBMA,oBACS,wEAOMC,WACjBC,SAASC,iBAAiB,SAASC,UACzBC,OAASD,EAAEC,OAAOC,QAAQN,yBAC3BK,SAAWA,OAAOE,aAAa,yBAGpCH,EAAEI,uBAEIC,UAAY,IAAIC,mBAAU,CAC5BC,YAAa,CACTC,MAAOP,OAAOE,aAAa,gBAE/BM,KAAM,CAACC,QAAST,OAAOE,aAAa,gBAAiBN,SAAAA,UACrDc,UAAWV,OAAOE,aAAa,kBAC/BS,YAAaX,SAKjBI,UAAUN,iBAAiBM,UAAUQ,OAAOC,gBAAgB,KACxDC,SAASC,YAGbX,UAAUY"}PK'9\> ]JJamd/build/add_modal.min.js.mapnu[{"version":3,"file":"add_modal.min.js","sources":["../src/add_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Show an add block modal instead of doing it on a separate page.\n *\n * @module core_block/add_modal\n * @copyright 2016 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\nimport Ajax from 'core/ajax';\nimport ModalForm from \"core_form/modalform\";\nimport CancelModal from 'core/modal_cancel';\n\nconst SELECTORS = {\n ADD_BLOCK: '[data-key=\"addblock\"]',\n SHOW_BLOCK_FORM: '[data-action=\"showaddblockform\"][data-blockname][data-blockform]'\n};\n\n// Ensure we only add our listeners once.\nlet listenerEventsRegistered = false;\n\n/**\n * Register related event listeners.\n *\n * @method registerListenerEvents\n * @param {String|null} addBlockUrl The add block URL\n * @param {String} pagehash\n */\nconst registerListenerEvents = (addBlockUrl, pagehash) => {\n let addBlockModal = null;\n document.addEventListener('click', e => {\n\n const showAddBlockForm = e.target.closest(SELECTORS.SHOW_BLOCK_FORM);\n if (showAddBlockForm) {\n e.preventDefault();\n\n const modalForm = new ModalForm({\n modalConfig: {\n title: getString('addblock', 'core_block',\n showAddBlockForm.getAttribute('data-blocktitle')),\n },\n args: {blockname: showAddBlockForm.getAttribute('data-blockname'), pagehash,\n blockregion: showAddBlockForm.getAttribute('data-blockregion')},\n formClass: showAddBlockForm.getAttribute('data-blockform'),\n returnFocus: showAddBlockForm,\n });\n\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => {\n addBlockModal.destroy();\n window.location.reload();\n });\n\n modalForm.show();\n }\n\n const addBlock = e.target.closest(SELECTORS.ADD_BLOCK);\n if (addBlock) {\n e.preventDefault();\n\n let addBlockModalUrl = addBlockUrl ?? addBlock.dataset.url;\n\n buildAddBlockModal()\n .then(modal => {\n addBlockModal = modal;\n const modalBody = renderBlocks(addBlockModalUrl, pagehash,\n addBlock.getAttribute('data-blockregion'));\n modal.setBody(modalBody);\n modal.show();\n\n return modalBody;\n })\n .catch(() => {\n addBlockModal.destroy();\n });\n }\n });\n};\n\n/**\n * Method that creates the 'add block' modal.\n *\n * @method buildAddBlockModal\n * @returns {Promise} The modal promise (modal's body will be rendered later).\n */\nconst buildAddBlockModal = () => CancelModal.create({\n title: getString('addblock'),\n});\n\n/**\n * Method that renders the list of available blocks.\n *\n * @method renderBlocks\n * @param {String} addBlockUrl The add block URL\n * @param {String} pagehash\n * @param {String} region\n * @return {Promise}\n */\nconst renderBlocks = async(addBlockUrl, pagehash, region) => {\n // Fetch all addable blocks in the given page.\n const blocks = await getAddableBlocks(pagehash);\n\n return Templates.render('core/add_block_body', {\n blocks: blocks,\n url: addBlockUrl,\n blockregion: region,\n pagehash\n });\n};\n\n/**\n * Method that fetches all addable blocks in a given page.\n *\n * @method getAddableBlocks\n * @param {String} pagehash\n * @return {Promise}\n */\nconst getAddableBlocks = async(pagehash) => {\n const request = {\n methodname: 'core_block_fetch_addable_blocks',\n args: {\n pagecontextid: 0,\n pagetype: '',\n pagelayout: '',\n subpage: '',\n pagehash: pagehash,\n },\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Set up the actions.\n *\n * @method init\n * @param {String} addBlockUrl The add block URL\n * @param {String} pagehash\n */\nexport const init = (addBlockUrl = null, pagehash = '') => {\n if (!listenerEventsRegistered) {\n registerListenerEvents(addBlockUrl, pagehash);\n listenerEventsRegistered = true;\n }\n};\n"],"names":["SELECTORS","listenerEventsRegistered","registerListenerEvents","addBlockUrl","pagehash","addBlockModal","document","addEventListener","e","showAddBlockForm","target","closest","preventDefault","modalForm","ModalForm","modalConfig","title","getAttribute","args","blockname","blockregion","formClass","returnFocus","events","FORM_SUBMITTED","destroy","window","location","reload","show","addBlock","addBlockModalUrl","dataset","url","buildAddBlockModal","then","modal","modalBody","renderBlocks","setBody","catch","CancelModal","create","async","region","blocks","getAddableBlocks","Templates","render","request","methodname","pagecontextid","pagetype","pagelayout","subpage","Ajax","call"],"mappings":";;;;;;;4QA6BMA,oBACS,wBADTA,0BAEe,uEAIjBC,0BAA2B,QASzBC,uBAAyB,CAACC,YAAaC,gBACrCC,cAAgB,KACpBC,SAASC,iBAAiB,SAASC,UAEzBC,iBAAmBD,EAAEE,OAAOC,QAAQX,8BACtCS,iBAAkB,CAClBD,EAAEI,uBAEIC,UAAY,IAAIC,mBAAU,CAC5BC,YAAa,CACTC,OAAO,kBAAU,WAAY,aACzBP,iBAAiBQ,aAAa,qBAEtCC,KAAM,CAACC,UAAWV,iBAAiBQ,aAAa,kBAAmBb,SAAAA,SAC/DgB,YAAaX,iBAAiBQ,aAAa,qBAC/CI,UAAWZ,iBAAiBQ,aAAa,kBACzCK,YAAab,mBAGjBI,UAAUN,iBAAiBM,UAAUU,OAAOC,gBAAgB,KACxDnB,cAAcoB,UACdC,OAAOC,SAASC,YAGpBf,UAAUgB,aAGRC,SAAWtB,EAAEE,OAAOC,QAAQX,wBAC9B8B,SAAU,CACVtB,EAAEI,qBAEEmB,iBAAmB5B,MAAAA,YAAAA,YAAe2B,SAASE,QAAQC,IAEvDC,qBACKC,MAAKC,QACF/B,cAAgB+B,YACVC,UAAYC,aAAaP,iBAAkB3B,SAC7C0B,SAASb,aAAa,4BAC1BmB,MAAMG,QAAQF,WACdD,MAAMP,OAECQ,aAEVG,OAAM,KACHnC,cAAcoB,kBAY5BS,mBAAqB,IAAMO,sBAAYC,OAAO,CAChD1B,OAAO,kBAAU,cAYfsB,aAAeK,MAAMxC,YAAaC,SAAUwC,gBAExCC,aAAeC,iBAAiB1C,iBAE/B2C,mBAAUC,OAAO,sBAAuB,CAC3CH,OAAQA,OACRZ,IAAK9B,YACLiB,YAAawB,OACbxC,SAAAA,YAWF0C,iBAAmBH,MAAAA,iBACfM,QAAU,CACZC,WAAY,kCACZhC,KAAM,CACFiC,cAAe,EACfC,SAAU,GACVC,WAAY,GACZC,QAAS,GACTlD,SAAUA,kBAIXmD,cAAKC,KAAK,CAACP,UAAU,kBAUZ,eAAC9C,mEAAc,KAAMC,gEAAW,GAC3CH,2BACDC,uBAAuBC,YAAaC,UACpCH,0BAA2B"}PK'9\dD amd/build/add_modal.min.jsnu[define("core_block/add_modal",["exports","core/templates","core/str","core/ajax","core_form/modalform","core/modal_cancel"],(function(_exports,_templates,_str,_ajax,_modalform,_modal_cancel){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Show an add block modal instead of doing it on a separate page. * * @module core_block/add_modal * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_ajax=_interopRequireDefault(_ajax),_modalform=_interopRequireDefault(_modalform),_modal_cancel=_interopRequireDefault(_modal_cancel);const SELECTORS_ADD_BLOCK='[data-key="addblock"]',SELECTORS_SHOW_BLOCK_FORM='[data-action="showaddblockform"][data-blockname][data-blockform]';let listenerEventsRegistered=!1;const registerListenerEvents=(addBlockUrl,pagehash)=>{let addBlockModal=null;document.addEventListener("click",(e=>{const showAddBlockForm=e.target.closest(SELECTORS_SHOW_BLOCK_FORM);if(showAddBlockForm){e.preventDefault();const modalForm=new _modalform.default({modalConfig:{title:(0,_str.getString)("addblock","core_block",showAddBlockForm.getAttribute("data-blocktitle"))},args:{blockname:showAddBlockForm.getAttribute("data-blockname"),pagehash:pagehash,blockregion:showAddBlockForm.getAttribute("data-blockregion")},formClass:showAddBlockForm.getAttribute("data-blockform"),returnFocus:showAddBlockForm});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(()=>{addBlockModal.destroy(),window.location.reload()})),modalForm.show()}const addBlock=e.target.closest(SELECTORS_ADD_BLOCK);if(addBlock){e.preventDefault();let addBlockModalUrl=null!=addBlockUrl?addBlockUrl:addBlock.dataset.url;buildAddBlockModal().then((modal=>{addBlockModal=modal;const modalBody=renderBlocks(addBlockModalUrl,pagehash,addBlock.getAttribute("data-blockregion"));return modal.setBody(modalBody),modal.show(),modalBody})).catch((()=>{addBlockModal.destroy()}))}}))},buildAddBlockModal=()=>_modal_cancel.default.create({title:(0,_str.getString)("addblock")}),renderBlocks=async(addBlockUrl,pagehash,region)=>{const blocks=await getAddableBlocks(pagehash);return _templates.default.render("core/add_block_body",{blocks:blocks,url:addBlockUrl,blockregion:region,pagehash:pagehash})},getAddableBlocks=async pagehash=>{const request={methodname:"core_block_fetch_addable_blocks",args:{pagecontextid:0,pagetype:"",pagelayout:"",subpage:"",pagehash:pagehash}};return _ajax.default.call([request])[0]};_exports.init=function(){let addBlockUrl=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,pagehash=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";listenerEventsRegistered||(registerListenerEvents(addBlockUrl,pagehash),listenerEventsRegistered=!0)}})); //# sourceMappingURL=add_modal.min.js.mapPK'9\t amd/build/events.min.js.mapnu[{"version":3,"file":"events.min.js","sources":["../src/events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript events for the `core_block` subsystem.\n *\n * @module core_block/events\n * @copyright 2021 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.0\n *\n * @example Example of listening to a block event.\n * import {eventTypes as blockEventTypes} from 'core_block/events';\n *\n * document.addEventListener(blockEventTypes.blockContentUpdated, e => {\n * window.console.log(e.target); // The HTMLElement relating to the block whose content was updated.\n * window.console.log(e.detail.instanceId); // The instanceId of the block that was updated.\n * });\n */\n\nimport {dispatchEvent} from 'core/event_dispatcher';\n\n/**\n * Events for `core_block`.\n *\n * @constant\n * @property {String} blockContentUpdated See {@link event:blockContentUpdated}\n */\nexport const eventTypes = {\n /**\n * An event triggered when the content of a block has changed.\n *\n * @event blockContentUpdated\n * @type {CustomEvent}\n * @property {HTMLElement} target The block element that was updated\n * @property {object} detail\n * @property {number} detail.instanceId The block instance id\n */\n blockContentUpdated: 'core_block/contentUpdated',\n};\n\n/**\n * Trigger an event to indicate that the content of a block was updated.\n *\n * @method notifyBlockContentUpdated\n * @param {HTMLElement} element The HTMLElement containing the updated block.\n * @returns {CustomEvent}\n * @fires blockContentUpdated\n */\nexport const notifyBlockContentUpdated = element => dispatchEvent(\n eventTypes.blockContentUpdated,\n {\n instanceId: element.dataset.instanceId,\n },\n element\n);\n"],"names":["eventTypes","blockContentUpdated","element","instanceId","dataset"],"mappings":";;;;;;;;;;;;;;;;;MAuCaA,WAAa,CAUtBC,oBAAqB,+FAWgBC,UAAW,mCAChDF,WAAWC,oBACX,CACIE,WAAYD,QAAQE,QAAQD,YAEhCD"}PK'9\Vamd/build/events.min.jsnu[define("core_block/events",["exports","core/event_dispatcher"],(function(_exports,_event_dispatcher){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.notifyBlockContentUpdated=_exports.eventTypes=void 0; /** * Javascript events for the `core_block` subsystem. * * @module core_block/events * @copyright 2021 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 4.0 * * @example Example of listening to a block event. * import {eventTypes as blockEventTypes} from 'core_block/events'; * * document.addEventListener(blockEventTypes.blockContentUpdated, e => { * window.console.log(e.target); // The HTMLElement relating to the block whose content was updated. * window.console.log(e.detail.instanceId); // The instanceId of the block that was updated. * }); */ const eventTypes={blockContentUpdated:"core_block/contentUpdated"};_exports.eventTypes=eventTypes;_exports.notifyBlockContentUpdated=element=>(0,_event_dispatcher.dispatchEvent)(eventTypes.blockContentUpdated,{instanceId:element.dataset.instanceId},element)})); //# sourceMappingURL=events.min.js.mapPK'9\t  comments/db/access.phpnu[. /** * Comments block caps. * * @package block_comments * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/comments:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/comments:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\M%#comments/lang/en/block_comments.phpnu[. /** * Strings for component 'block_comments', language 'en', branch 'MOODLE_20_STABLE' * * @package block_comments * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['comments:myaddinstance'] = 'Add a new comments block to Dashboard'; $string['comments:addinstance'] = 'Add a new comments block'; $string['pluginname'] = 'Comments'; $string['privacy:metadata:core_comment'] = 'A record of comments added.'; PK'9\" " comments/lib.phpnu[. /** * The comments block helper functions and callbacks * * @package block_comments * @copyright 2011 Dongsheng Cai * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Validate comment parameter before perform other comments actions * * @package block_comments * @category comment * * @param stdClass $comment_param { * context => context the context object * courseid => int course id * cm => stdClass course module object * commentarea => string comment area * itemid => int itemid * } * @return boolean */ function block_comments_comment_validate($comment_param) { if ($comment_param->commentarea != 'page_comments') { throw new comment_exception('invalidcommentarea'); } if ($comment_param->itemid != 0) { throw new comment_exception('invalidcommentitemid'); } return true; } /** * Running addtional permission check on plugins * * @package block_comments * @category comment * * @param stdClass $args * @return array */ function block_comments_comment_permissions($args) { global $DB, $USER; // By default, anyone can post and view comments. $canpost = $canview = true; // Check if it's the user context and not the owner's profile. if ($args->context->contextlevel == CONTEXT_USER && $USER->id != $args->context->instanceid) { // Check whether the context owner has a comment block in the user's profile. $sqlparam = [ 'blockname' => 'comments', 'parentcontextid' => $args->context->id, 'pagetypepattern' => 'user-profile', ]; // If the comment block is not present at the target user's profile, // then the logged-in user cannot post or view comments. $canpost = $canview = $DB->record_exists_select( 'block_instances', 'blockname = :blockname AND parentcontextid = :parentcontextid AND pagetypepattern = :pagetypepattern', $sqlparam, ); } return ['post' => $canpost, 'view' => $canview]; } /** * Validate comment data before displaying comments * * @package block_comments * @category comment * * @param stdClass $comment * @param stdClass $args * @return boolean */ function block_comments_comment_display($comments, $args) { if ($args->commentarea != 'page_comments') { throw new comment_exception('invalidcommentarea'); } if ($args->itemid != 0) { throw new comment_exception('invalidcommentitemid'); } return $comments; } PK'9\HXcomments/version.phpnu[. /** * Version details * * @package block_comments * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_comments'; // Full name of the plugin (used for diagnostics) PK'9\ comments/block_comments.phpnu[. /** * The comments block * * @package block_comments * @copyright 2009 Dongsheng Cai * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_comments extends block_base { function init() { global $CFG; require_once($CFG->dirroot . '/comment/lib.php'); $this->title = get_string('pluginname', 'block_comments'); } function specialization() { // require js for commenting comment::init(); } function applicable_formats() { return array('all' => true); } function instance_allow_multiple() { return false; } function get_content() { global $CFG; if ($this->content !== NULL) { return $this->content; } if (!$CFG->usecomments) { $this->content = new stdClass(); $this->content->text = ''; if ($this->page->user_is_editing()) { $this->content->text = get_string('disabledcomments'); } return $this->content; } $this->content = new stdClass(); $this->content->footer = ''; $this->content->text = ''; if (empty($this->instance)) { return $this->content; } list($context, $course, $cm) = get_context_info_array($this->page->context->id); $args = new stdClass; $args->context = $this->page->context; $args->course = $course; $args->area = 'page_comments'; $args->itemid = 0; $args->component = 'block_comments'; $args->linktext = get_string('showcomments'); $args->notoggle = true; $args->autostart = true; $args->displaycancel = false; $comment = new comment($args); $comment->set_view_permission(true); $comment->set_fullwidth(); $this->content = new stdClass(); $this->content->text = $comment->output(true); $this->content->footer = ''; return $this->content; } /** * This block shouldn't be added to a page if the comments advanced feature is disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { global $CFG; return $CFG->usecomments; } } PK'9\4kk%comments/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_comments. * * @package block_comments * @category privacy * @copyright 2018 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_comments\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\contextlist; use core_privacy\local\request\userlist; use core_privacy\local\request\approved_userlist; /** * Privacy Subsystem implementation for block_comments. * * @copyright 2018 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements // The block_comments block stores user provided data. \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, // The block_comments block provides data directly to core. \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection * @return collection */ public static function get_metadata(collection $collection): collection { return $collection->add_subsystem_link('core_comment', [], 'privacy:metadata:core_comment'); } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid * @return contextlist */ public static function get_contexts_for_userid(int $userid): contextlist { $contextlist = new contextlist(); $sql = "SELECT contextid FROM {comments} WHERE component = :component AND userid = :userid"; $params = [ 'component' => 'block_comments', 'userid' => $userid ]; $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users within a specific context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); $params = [ 'contextid' => $context->id, 'component' => 'block_comments', ]; $sql = "SELECT userid as userid FROM {comments} WHERE component = :component AND contextid = :contextid"; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist */ public static function export_user_data(approved_contextlist $contextlist) { $contexts = $contextlist->get_contexts(); foreach ($contexts as $context) { \core_comment\privacy\provider::export_comments( $context, 'block_comments', 'page_comments', 0, [] ); } } /** * Delete all data for all users in the specified context. * * @param \context $context */ public static function delete_data_for_all_users_in_context(\context $context) { \core_comment\privacy\provider::delete_comments_for_all_users($context, 'block_comments'); } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { \core_comment\privacy\provider::delete_comments_for_users($userlist, 'block_comments'); } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist */ public static function delete_data_for_user(approved_contextlist $contextlist) { \core_comment\privacy\provider::delete_comments_for_user($contextlist, 'block_comments'); } } PK'9\C|*comments/classes/event/comment_deleted.phpnu[. /** * block_comments comment deleted event. * * @package block_comments * @copyright 2013 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_comments\event; defined('MOODLE_INTERNAL') || die(); /** * block_comments comment deleted event. * * @package block_comments * @since Moodle 2.7 * @copyright 2013 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class comment_deleted extends \core\event\comment_deleted { // No need to override any method. } PK'9\!*comments/classes/event/comment_created.phpnu[. /** * block_comments comment created event. * * @package block_comments * @copyright 2013 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_comments\event; defined('MOODLE_INTERNAL') || die(); /** * block_comments comment created event. * * @package block_comments * @since Moodle 2.7 * @copyright 2013 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class comment_created extends \core\event\comment_created { // No need to override any method. } PK'9\#q^^(comments/tests/privacy/provider_test.phpnu[. /** * Privacy provider tests. * * @package block_comments * @copyright 2018 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_comments\privacy; use core_privacy\local\metadata\collection; use block_comments\privacy\provider; use core_privacy\local\request\approved_userlist; use stdClass; defined('MOODLE_INTERNAL') || die(); /** * Privacy provider test for block_comments. * * @copyright 2018 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends \core_privacy\tests\provider_testcase { /** @var stdClass A student who is only enrolled in course1. */ protected $student1; /** @var stdClass A student who is only enrolled in course2. */ protected $student2; /** @var stdClass A student who is enrolled in both course1 and course2. */ protected $student12; /** @var stdClass A test course. */ protected $course1; /** @var stdClass A test course. */ protected $course2; protected function setUp(): void { global $DB; parent::setUp(); $this->resetAfterTest(); $this->setAdminUser(); // Create courses. $generator = $this->getDataGenerator(); $this->course1 = $generator->create_course(); $this->course2 = $generator->create_course(); // Create and enrol students. $this->student1 = $generator->create_user(); $this->student2 = $generator->create_user(); $this->student12 = $generator->create_user(); $studentrole = $DB->get_record('role', ['shortname' => 'student']); $generator->enrol_user($this->student1->id, $this->course1->id, $studentrole->id); $generator->enrol_user($this->student2->id, $this->course2->id, $studentrole->id); $generator->enrol_user($this->student12->id, $this->course1->id, $studentrole->id); $generator->enrol_user($this->student12->id, $this->course2->id, $studentrole->id); // Comment block on course pages. $block = $this->add_comments_block_in_context(\context_course::instance($this->course1->id)); $block = $this->add_comments_block_in_context(\context_course::instance($this->course2->id)); } /** * Posts a comment on a given context. * * @param string $text The comment's text. * @param \context $context The context on which we want to put the comment. */ protected function add_comment($text, \context $context) { $args = new \stdClass; $args->context = $context; $args->area = 'page_comments'; $args->itemid = 0; $args->component = 'block_comments'; $args->linktext = get_string('showcomments'); $args->notoggle = true; $args->autostart = true; $args->displaycancel = false; $comment = new \comment($args); $comment->add($text); } /** * Creates a comments block on a context. * * @param \context $context The context on which we want to put the block. * @return \block_base The created block instance. * @throws \coding_exception */ protected function add_comments_block_in_context(\context $context) { global $DB; $course = null; $page = new \moodle_page(); $page->set_context($context); switch ($context->contextlevel) { case CONTEXT_SYSTEM: $page->set_pagelayout('frontpage'); $page->set_pagetype('site-index'); break; case CONTEXT_COURSE: $page->set_pagelayout('standard'); $page->set_pagetype('course-view'); $course = $DB->get_record('course', ['id' => $context->instanceid]); $page->set_course($course); break; case CONTEXT_MODULE: $page->set_pagelayout('standard'); $mod = $DB->get_field_sql("SELECT m.name FROM {modules} m JOIN {course_modules} cm on cm.module = m.id WHERE cm.id = ?", [$context->instanceid]); $page->set_pagetype("mod-$mod-view"); break; case CONTEXT_USER: $page->set_pagelayout('mydashboard'); $page->set_pagetype('my-index'); break; default: throw new coding_exception('Unsupported context for test'); } $page->blocks->load_blocks(); $page->blocks->add_block_at_end_of_default_region('comments'); // We need to use another page object as load_blocks() only loads the blocks once. $page2 = new \moodle_page(); $page2->set_context($page->context); $page2->set_pagelayout($page->pagelayout); $page2->set_pagetype($page->pagetype); if ($course) { $page2->set_course($course); } $page->blocks->load_blocks(); $page2->blocks->load_blocks(); $blocks = $page2->blocks->get_blocks_for_region($page2->blocks->get_default_region()); $block = end($blocks); $block = block_instance('comments', $block->instance); return $block; } /** * Test for provider::get_metadata(). */ public function test_get_metadata(): void { $collection = new collection('block_comments'); $newcollection = provider::get_metadata($collection); $itemcollection = $newcollection->get_collection(); $this->assertCount(1, $itemcollection); $link = reset($itemcollection); $this->assertEquals('core_comment', $link->get_name()); $this->assertEmpty($link->get_privacy_fields()); $this->assertEquals('privacy:metadata:core_comment', $link->get_summary()); } /** * Test for provider::get_contexts_for_userid() when user had not posted any comments.. */ public function test_get_contexts_for_userid_no_comment(): void { $this->setUser($this->student1); $coursecontext1 = \context_course::instance($this->course1->id); $this->add_comment('New comment', $coursecontext1); $this->setUser($this->student2); $contextlist = provider::get_contexts_for_userid($this->student2->id); $this->assertCount(0, $contextlist); } /** * Test for provider::get_contexts_for_userid(). */ public function test_get_contexts_for_userid(): void { $coursecontext1 = \context_course::instance($this->course1->id); $coursecontext2 = \context_course::instance($this->course2->id); $this->setUser($this->student12); $this->add_comment('New comment', $coursecontext1); $this->add_comment('New comment', $coursecontext1); $this->add_comment('New comment', $coursecontext2); $contextlist = provider::get_contexts_for_userid($this->student12->id); $this->assertCount(2, $contextlist); $contextids = $contextlist->get_contextids(); $this->assertEqualsCanonicalizing([$coursecontext1->id, $coursecontext2->id], array_values($contextids)); } /** * Test for provider::export_user_data() when the user has not posted any comments. */ public function test_export_for_context_no_comment(): void { $coursecontext1 = \context_course::instance($this->course1->id); $coursecontext2 = \context_course::instance($this->course2->id); $this->setUser($this->student1); $this->add_comment('New comment', $coursecontext1); $this->setUser($this->student2); $this->setUser($this->student2); $this->export_context_data_for_user($this->student2->id, $coursecontext2, 'block_comments'); $writer = \core_privacy\local\request\writer::with_context($coursecontext2); $this->assertFalse($writer->has_any_data()); } /** * Test for provider::export_user_data(). */ public function test_export_for_context(): void { $coursecontext1 = \context_course::instance($this->course1->id); $coursecontext2 = \context_course::instance($this->course2->id); $this->setUser($this->student12); $this->add_comment('New comment', $coursecontext1); $this->add_comment('New comment', $coursecontext1); $this->add_comment('New comment', $coursecontext2); // Export all of the data for the context. $this->export_context_data_for_user($this->student12->id, $coursecontext1, 'block_comments'); $writer = \core_privacy\local\request\writer::with_context($coursecontext1); $this->assertTrue($writer->has_any_data()); } /** * Test for provider::delete_data_for_all_users_in_context(). */ public function test_delete_data_for_all_users_in_context(): void { global $DB; $coursecontext1 = \context_course::instance($this->course1->id); $coursecontext2 = \context_course::instance($this->course2->id); $this->setUser($this->student1); $this->add_comment('New comment', $coursecontext1); $this->setUser($this->student2); $this->add_comment('New comment', $coursecontext2); $this->setUser($this->student12); $this->add_comment('New comment', $coursecontext1); $this->add_comment('New comment', $coursecontext1); $this->add_comment('New comment', $coursecontext2); // Before deletion, we should have 3 comments in $coursecontext1 and 2 comments in $coursecontext2. $this->assertEquals( 3, $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext1->id]) ); $this->assertEquals( 2, $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext2->id]) ); // Delete data based on context. provider::delete_data_for_all_users_in_context($coursecontext1); // After deletion, the comments for $coursecontext1 should have been deleted. $this->assertEquals( 0, $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext1->id]) ); $this->assertEquals( 2, $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext2->id]) ); } /** * Test for provider::delete_data_for_all_users_in_context() when there are also comments from other plugins. */ public function test_delete_data_for_all_users_in_context_with_comments_from_other_plugins(): void { global $DB; $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); $instance = $assigngenerator->create_instance(['course' => $this->course1]); $cm = get_coursemodule_from_instance('assign', $instance->id); $assigncontext = \context_module::instance($cm->id); $assign = new \assign($assigncontext, $cm, $this->course1); // Add a comments block in the assignment page. $this->add_comments_block_in_context($assigncontext); $submission = $assign->get_user_submission($this->student1->id, true); $options = new \stdClass(); $options->area = 'submission_comments'; $options->course = $assign->get_course(); $options->context = $assigncontext; $options->itemid = $submission->id; $options->component = 'assignsubmission_comments'; $options->showcount = true; $options->displaycancel = true; $comment = new \comment($options); $comment->set_post_permission(true); $this->setUser($this->student1); $comment->add('Comment from student 1'); $this->add_comment('New comment', $assigncontext); $this->setUser($this->student2); $this->add_comment('New comment', $assigncontext); // Before deletion, we should have 3 comments in $assigncontext. // One comment is for the assignment submission and 2 are for the comments block. $this->assertEquals( 3, $DB->count_records('comments', ['contextid' => $assigncontext->id]) ); $this->assertEquals( 2, $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $assigncontext->id]) ); provider::delete_data_for_all_users_in_context($assigncontext); // After deletion, the comments for $assigncontext in the comment block should have been deleted, // but the assignment submission comment should be left. $this->assertEquals( 1, $DB->count_records('comments', ['contextid' => $assigncontext->id]) ); $this->assertEquals( 0, $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $assigncontext->id]) ); } /** * Test for provider::delete_data_for_user(). */ public function test_delete_data_for_user(): void { global $DB; $coursecontext1 = \context_course::instance($this->course1->id); $coursecontext2 = \context_course::instance($this->course2->id); $this->setUser($this->student1); $this->add_comment('New comment', $coursecontext1); $this->setUser($this->student2); $this->add_comment('New comment', $coursecontext2); $this->setUser($this->student12); $this->add_comment('New comment', $coursecontext1); $this->add_comment('New comment', $coursecontext1); $this->add_comment('New comment', $coursecontext2); // Before deletion, we should have 3 comments in $coursecontext1 and 2 comments in $coursecontext2, // and 3 comments by student12 in $coursecontext1 and $coursecontext2 combined. $this->assertEquals( 3, $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext1->id]) ); $this->assertEquals( 2, $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext2->id]) ); $this->assertEquals( 3, $DB->count_records('comments', ['component' => 'block_comments', 'userid' => $this->student12->id]) ); $contextlist = new \core_privacy\local\request\approved_contextlist($this->student12, 'block_comments', [$coursecontext1->id, $coursecontext2->id]); provider::delete_data_for_user($contextlist); // After deletion, the comments for the student12 should have been deleted. $this->assertEquals( 1, $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext1->id]) ); $this->assertEquals( 1, $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext2->id]) ); $this->assertEquals( 0, $DB->count_records('comments', ['component' => 'block_comments', 'userid' => $this->student12->id]) ); } /** * Test for provider::delete_data_for_user() when there are also comments from other plugins. */ public function test_delete_data_for_user_with_comments_from_other_plugins(): void { global $DB; $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); $instance = $assigngenerator->create_instance(['course' => $this->course1]); $cm = get_coursemodule_from_instance('assign', $instance->id); $assigncontext = \context_module::instance($cm->id); $assign = new \assign($assigncontext, $cm, $this->course1); // Add a comments block in the assignment page. $this->add_comments_block_in_context($assigncontext); $submission = $assign->get_user_submission($this->student1->id, true); $options = new \stdClass(); $options->area = 'submission_comments'; $options->course = $assign->get_course(); $options->context = $assigncontext; $options->itemid = $submission->id; $options->component = 'assignsubmission_comments'; $options->showcount = true; $options->displaycancel = true; $comment = new \comment($options); $comment->set_post_permission(true); $this->setUser($this->student1); $comment->add('Comment from student 1'); $this->add_comment('New comment', $assigncontext); $this->add_comment('New comment', $assigncontext); // Before deletion, we should have 3 comments in $assigncontext. // one comment is for the assignment submission and 2 are for the comments block. $this->assertEquals( 3, $DB->count_records('comments', ['contextid' => $assigncontext->id]) ); $contextlist = new \core_privacy\local\request\approved_contextlist($this->student1, 'block_comments', [$assigncontext->id]); provider::delete_data_for_user($contextlist); // After deletion, the comments for the student1 in the comment block should have been deleted, // but the assignment submission comment should be left. $this->assertEquals( 1, $DB->count_records('comments', ['contextid' => $assigncontext->id]) ); $this->assertEquals( 0, $DB->count_records('comments', ['component' => 'block_comments', 'userid' => $this->student1->id]) ); } /** * Test that only users within a course context are fetched. * @group qtesttt */ public function test_get_users_in_context(): void { $component = 'block_comments'; $coursecontext1 = \context_course::instance($this->course1->id); $coursecontext2 = \context_course::instance($this->course2->id); $userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(0, $userlist1); $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(0, $userlist2); $this->setUser($this->student12); $this->add_comment('New comment', $coursecontext1); $this->add_comment('New comment', $coursecontext2); $this->setUser($this->student1); $this->add_comment('New comment', $coursecontext1); // The list of users should contain user12 and user1. provider::get_users_in_context($userlist1); $this->assertCount(2, $userlist1); $this->assertTrue(in_array($this->student1->id, $userlist1->get_userids())); $this->assertTrue(in_array($this->student12->id, $userlist1->get_userids())); // The list of users should contain user12. provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); $expected = [$this->student12->id]; $actual = $userlist2->get_userids(); $this->assertEquals($expected, $actual); } /** * Test that data for users in approved userlist is deleted. */ public function test_delete_data_for_users(): void { $component = 'block_comments'; $coursecontext1 = \context_course::instance($this->course1->id); $coursecontext2 = \context_course::instance($this->course2->id); $this->setUser($this->student12); $this->add_comment('New comment', $coursecontext1); $this->add_comment('New comment', $coursecontext2); $this->setUser($this->student1); $this->add_comment('New comment', $coursecontext1); $userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(2, $userlist1); $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); // Convert $userlist1 into an approved_contextlist. $approvedlist1 = new approved_userlist($coursecontext1, $component, $userlist1->get_userids()); // Delete using delete_data_for_user. provider::delete_data_for_users($approvedlist1); // Re-fetch users in coursecontext1. $userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component); provider::get_users_in_context($userlist1); // The user data in coursecontext1 should be deleted. $this->assertCount(0, $userlist1); // Re-fetch users in coursecontext2. $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component); provider::get_users_in_context($userlist2); // The user data in coursecontext2 should be still present. $this->assertCount(1, $userlist2); } /** * Test for provider::delete_data_for_user() when there are also comments from other plugins. */ public function test_delete_data_for_users_with_comments_from_other_plugins(): void { $component = 'block_comments'; $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); $instance = $assigngenerator->create_instance(['course' => $this->course1]); $cm = get_coursemodule_from_instance('assign', $instance->id); $assigncontext = \context_module::instance($cm->id); $assign = new \assign($assigncontext, $cm, $this->course1); // Add a comments block in the assignment page. $this->add_comments_block_in_context($assigncontext); $submission = $assign->get_user_submission($this->student1->id, true); $options = new \stdClass(); $options->area = 'submission_comments'; $options->course = $assign->get_course(); $options->context = $assigncontext; $options->itemid = $submission->id; $options->component = 'assignsubmission_comments'; $options->showcount = true; $options->displaycancel = true; $comment = new \comment($options); $comment->set_post_permission(true); $this->setUser($this->student1); $comment->add('Comment from student 1'); $this->add_comment('New comment', $assigncontext); $this->add_comment('New comment', $assigncontext); $userlist1 = new \core_privacy\local\request\userlist($assigncontext, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); // Convert $userlist1 into an approved_contextlist. $approvedlist = new approved_userlist($assigncontext, $component, $userlist1->get_userids()); // Delete using delete_data_for_user. provider::delete_data_for_users($approvedlist); // Re-fetch users in assigncontext. $userlist1 = new \core_privacy\local\request\userlist($assigncontext, $component); provider::get_users_in_context($userlist1); // The user data in assigncontext should be deleted. $this->assertCount(0, $userlist1); } } PK'9\> comments/tests/comments_test.phpnu[. namespace block_comments; use advanced_testcase; use block_comments; use context_course; /** * PHPUnit block_comments tests * * @package block_comments * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_comments */ final class comments_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_comments.php'); parent::setUpBeforeClass(); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_comments(); // If comments advanced feature is enabled, the method should return true. set_config('usecomments', true); $this->assertTrue($block->can_block_be_added($page)); // However, if the comments advanced feature is disabled, the method should return false. set_config('usecomments', false); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\Hv$comments/tests/event/events_test.phpnu[. /** * Events tests. * * @package block_comments * @category test * @copyright 2013 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_comments\event; /** * Events tests class. * * @package block_comments * @category test * @copyright 2013 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class events_test extends \advanced_testcase { /** @var stdClass Keeps course object */ private $course; /** @var stdClass Keeps wiki object */ private $wiki; /** * Setup test data. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(); $this->setAdminUser(); // Create course and wiki. $this->course = $this->getDataGenerator()->create_course(); $this->wiki = $this->getDataGenerator()->create_module('wiki', array('course' => $this->course->id)); } /** * Test comment_created event. */ public function test_comment_created(): void { global $CFG; require_once($CFG->dirroot . '/comment/lib.php'); // Comment on course page. $context = \context_course::instance($this->course->id); $args = new \stdClass; $args->context = $context; $args->course = $this->course; $args->area = 'page_comments'; $args->itemid = 0; $args->component = 'block_comments'; $args->linktext = get_string('showcomments'); $args->notoggle = true; $args->autostart = true; $args->displaycancel = false; $comment = new \comment($args); // Triggering and capturing the event. $sink = $this->redirectEvents(); $comment->add('New comment'); $events = $sink->get_events(); $this->assertCount(1, $events); $event = reset($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\block_comments\event\comment_created', $event); $this->assertEquals($context, $event->get_context()); $url = new \moodle_url('/course/view.php', array('id' => $this->course->id)); $this->assertEquals($url, $event->get_url()); // Comments when block is on module (wiki) page. $context = \context_module::instance($this->wiki->cmid); $args = new \stdClass; $args->context = $context; $args->course = $this->course; $args->area = 'page_comments'; $args->itemid = 0; $args->component = 'block_comments'; $args->linktext = get_string('showcomments'); $args->notoggle = true; $args->autostart = true; $args->displaycancel = false; $comment = new \comment($args); // Triggering and capturing the event. $sink = $this->redirectEvents(); $comment->add('New comment 1'); $events = $sink->get_events(); $this->assertCount(1, $events); $event = reset($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\block_comments\event\comment_created', $event); $this->assertEquals($context, $event->get_context()); $url = new \moodle_url('/mod/wiki/view.php', array('id' => $this->wiki->cmid)); $this->assertEquals($url, $event->get_url()); $this->assertEventContextNotUsed($event); } /** * Test comment_deleted event. */ public function test_comment_deleted(): void { global $CFG; require_once($CFG->dirroot . '/comment/lib.php'); // Comment on course page. $context = \context_course::instance($this->course->id); $args = new \stdClass; $args->context = $context; $args->course = $this->course; $args->area = 'page_comments'; $args->itemid = 0; $args->component = 'block_comments'; $args->linktext = get_string('showcomments'); $args->notoggle = true; $args->autostart = true; $args->displaycancel = false; $comment = new \comment($args); $newcomment = $comment->add('New comment'); // Triggering and capturing the event. $sink = $this->redirectEvents(); $comment->delete($newcomment->id); $events = $sink->get_events(); $this->assertCount(1, $events); $event = reset($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\block_comments\event\comment_deleted', $event); $this->assertEquals($context, $event->get_context()); $url = new \moodle_url('/course/view.php', array('id' => $this->course->id)); $this->assertEquals($url, $event->get_url()); // Comments when block is on module (wiki) page. $context = \context_module::instance($this->wiki->cmid); $args = new \stdClass; $args->context = $context; $args->course = $this->course; $args->area = 'page_comments'; $args->itemid = 0; $args->component = 'block_comments'; $args->linktext = get_string('showcomments'); $args->notoggle = true; $args->autostart = true; $args->displaycancel = false; $comment = new \comment($args); $newcomment = $comment->add('New comment 1'); // Triggering and capturing the event. $sink = $this->redirectEvents(); $comment->delete($newcomment->id); $events = $sink->get_events(); $this->assertCount(1, $events); $event = reset($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\block_comments\event\comment_deleted', $event); $this->assertEquals($context, $event->get_context()); $url = new \moodle_url('/mod/wiki/view.php', array('id' => $this->wiki->cmid)); $this->assertEquals($url, $event->get_url()); $this->assertEventContextNotUsed($event); } } PK'9\>3comments/tests/behat/block_comment_activity.featurenu[@block @block_comments Feature: Enable Block comments on an activity page and view comments In order to enable the comments block on an activity page As a teacher I can add the comments block to an activity page Scenario: Add the comments block on an activity page and add comments Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "activities" exist: | activity | course | idnumber | name | intro | | page | C1 | page1 | Test page name | Test page description | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | comments | Activity module | page1 | mod-page-* | side-pre | And I am on the "Test page name" "page activity" page logged in as teacher1 And I follow "Show comments" And I add "I'm a comment from the teacher" comment to comments block When I am on the "Test page name" "page activity" page logged in as student1 And I follow "Show comments" Then I should see "I'm a comment from the teacher" PK'9\A(comments/tests/behat/add_comment.featurenu[@block @block_comments Feature: Add a comment to the comments block In order to comment on a conversation or a topic As a user In need to add comments to courses Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | comments | Course | C1 | course-view-* | side-pre | And I am on the "Course 1" course page logged in as student1 @javascript Scenario: Add a comment with Javascript enabled When I add "I'm a comment from student1" comment to comments block Then I should see "I'm a comment from student1" And I am on the "Course 1" course page logged in as teacher1 And I should see "I'm a comment from student1" Scenario: Add a comment with Javascript disabled When I follow "Show comments" And I add "I'm a comment from student1" comment to comments block Then I should see "I'm a comment from student1" And I am on the "Course 1" course page logged in as teacher1 And I follow "Show comments" And I should see "I'm a comment from student1" @javascript Scenario: Test comment block pagination When I add "Super test comment 01" comment to comments block And I change window size to "1024x4096" And I add "Super test comment 02" comment to comments block And I add "Super test comment 03" comment to comments block And I add "Super test comment 04" comment to comments block And I add "Super test comment 05" comment to comments block And I add "Super test comment 06" comment to comments block And I add "Super test comment 07" comment to comments block And I add "Super test comment 08" comment to comments block And I add "Super test comment 09" comment to comments block And I add "Super test comment 10" comment to comments block And I add "Super test comment 11" comment to comments block And I add "Super test comment 12" comment to comments block And I add "Super test comment 13" comment to comments block And I add "Super test comment 14" comment to comments block And I add "Super test comment 15" comment to comments block And I add "Super test comment 16" comment to comments block And I add "Super test comment 17" comment to comments block And I add "Super test comment 18" comment to comments block And I add "Super test comment 19" comment to comments block And I add "Super test comment 20" comment to comments block And I add "Super test comment 21" comment to comments block And I add "Super test comment 22" comment to comments block And I add "Super test comment 23" comment to comments block And I add "Super test comment 24" comment to comments block And I add "Super test comment 25" comment to comments block And I add "Super test comment 26" comment to comments block And I add "Super test comment 27" comment to comments block And I add "Super test comment 28" comment to comments block And I add "Super test comment 29" comment to comments block And I add "Super test comment 30" comment to comments block And I add "Super test comment 31" comment to comments block Then I should see "Super test comment 01" And I should see "Super test comment 31" And I am on "Course 1" course homepage And I should not see "Super test comment 01" And I should not see "Super test comment 02" And I should not see "Super test comment 16" And I should see "Super test comment 17" And I should see "Super test comment 31" And I should see "1" in the ".block_comments .comment-paging" "css_element" And I should see "2" in the ".block_comments .comment-paging" "css_element" And I should see "3" in the ".block_comments .comment-paging" "css_element" And I should not see "4" in the ".block_comments .comment-paging" "css_element" And I click on "2" "link" in the ".block_comments .comment-paging" "css_element" And I should not see "Super test comment 01" And I should see "Super test comment 02" And I should see "Super test comment 16" And I should not see "Super test comment 17" And I should not see "Super test comment 31" And I click on "3" "link" in the ".block_comments .comment-paging" "css_element" And I should see "Super test comment 01" And I should not see "Super test comment 02" And I should not see "Super test comment 16" And I should not see "Super test comment 17" And I should not see "Super test comment 31" And I click on "1" "link" in the ".block_comments .comment-paging" "css_element" And I should not see "Super test comment 01" And I should not see "Super test comment 02" And I should not see "Super test comment 16" And I should see "Super test comment 17" And I should see "Super test comment 31" PK'9\+4comments/tests/behat/block_comment_frontpage.featurenu[@block @block_comments Feature: Enable Block comments on the frontpage and view comments In order to enable the comments block on the frontpage As a admin I can add the comments block to the frontpage Scenario: Add the comments block on the frontpage and add comments Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | comments | System | 1 | site-index | side-pre | And I log in as "admin" And I am on site homepage And I follow "Show comments" And I add "I'm a comment from admin" comment to comments block And I log out When I log in as "teacher1" And I am on site homepage And I follow "Show comments" Then I should see "I'm a comment from admin" PK'9\ۊ-comments/tests/behat/behat_block_comments.phpnu[. /** * Commenting system steps definitions. * * @package block_comments * @category test * @copyright 2013 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException, Behat\Mink\Exception\ExpectationException as ExpectationException; /** * Steps definitions to deal with the commenting system * * @package block_comments * @category test * @copyright 2013 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_block_comments extends behat_base { /** * Adds the specified option to the comments block of the current page. * * This method can be adapted in future to add other comments considering * that there could be more than one comment textarea per page. * * Only 1 comments block instance is allowed per page, if this changes this * steps definitions should be adapted. * * @Given /^I add "(?P(?:[^"]|\\")*)" comment to comments block$/ * @throws ElementNotFoundException * @param string $comment */ public function i_add_comment_to_comments_block($comment) { // Getting the textarea and setting the provided value. $exception = new ElementNotFoundException($this->getSession(), 'Comments block '); // The whole DOM structure changes depending on JS enabled/disabled. if ($this->running_javascript()) { $commentstextarea = $this->find('css', '.comment-area textarea', $exception); $commentstextarea->setValue($comment); $this->find_link(get_string('savecomment'))->click(); // Delay after clicking so that additional comments will have unique time stamps. // We delay 1 second which is all we need. $this->getSession()->wait(1000); } else { $commentstextarea = $this->find('css', '.block_comments form textarea', $exception); $commentstextarea->setValue($comment); // Comments submit button $submit = $this->find('css', '.block_comments form input[type=submit]'); $submit->press(); } } /** * Deletes the specified comment from the current page's comments block. * * @Given /^I delete "(?P(?:[^"]|\\")*)" comment from comments block$/ * @throws ElementNotFoundException * @throws ExpectationException * @param string $comment */ public function i_delete_comment_from_comments_block($comment) { $exception = new ElementNotFoundException($this->getSession(), '"' . $comment . '" comment '); // Using xpath liternal to avoid possible problems with comments containing quotes. $commentliteral = behat_context_helper::escape($comment); $commentxpath = "//*[contains(concat(' ', normalize-space(@class), ' '), ' block_comments ')]" . "/descendant::div[@class='comment-message'][contains(., $commentliteral)]"; $commentnode = $this->find('xpath', $commentxpath, $exception); // Click on delete icon. $this->execute('behat_general::i_click_on_in_the', array("Delete comment posted by", "icon", $this->escape($commentxpath), "xpath_element") ); // Wait for the animation to finish, in theory is just 1 sec, adding 4 just in case. $this->getSession()->wait(4 * 1000); } } PK'9\TP4comments/tests/behat/block_comment_dashboard.featurenu[@block @block_comments Feature: Enable Block comments on the dashboard and view comments In order to enable the comments block on a the dashboard As a teacher I can add the comments block to my dashboard Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | First | teacher1@example.com | Scenario: Add the comments block on the dashboard and add comments with Javascript disabled When I log in as "teacher1" And I turn editing mode on And I add the "Comments" block And I follow "Show comments" And I add "I'm a comment from the teacher" comment to comments block Then I should see "I'm a comment from the teacher" @javascript Scenario: Add the comments block on the dashboard and add comments with Javascript enabled When I log in as "teacher1" And I turn editing mode on And I add the "Comments" block And I add "I'm a comment from the teacher" comment to comments block Then I should see "I'm a comment from the teacher" PK'9\ dmkyy+comments/tests/behat/delete_comment.featurenu[@block @block_comments Feature: Delete comment block messages In order to refine comment block's contents As a teacher In need to delete comments from courses @javascript Scenario: Delete comments with Javascript enabled Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | comments | Course | C1 | course-view-* | side-pre | And I am on the "Course 1" course page logged in as student1 And I add "Comment from student1" comment to comments block And I am on the "Course 1" course page logged in as teacher1 And I add "Comment from teacher1" comment to comments block When I delete "Comment from student1" comment from comments block Then I should not see "Comment from student1" And I delete "Comment from teacher1" comment from comments block And I should not see "Comment from teacher1" PK'9\? UPGRADING.mdnu[# core_block (subsystem / plugintype) Upgrade notes ## 5.0 ### Removed - Removed block_mnet_hosts plugin from core For more information see [MDL-84309](https://tracker.moodle.org/browse/MDL-84309) PK'9\Htag_youtube/styles.cssnu[.block_tag_youtube .youtube-thumb { padding: 3px; padding-bottom: 0.5em; display: block; float: left; } .block_tag_youtube .yt-video-entry li { clear: left; }PK'9\X(Xtag_youtube/db/install.phpnu[. /** * Tag Youtube block installation. * * @package block_tag_youtube * @copyright 2015 Jun Pataleta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Sets the install values for the tag_youtube entry in the block table. * * @return void */ function xmldb_block_tag_youtube_install() { global $DB; // Disable this block by default. $DB->set_field('block', 'visible', 0, array('name' => 'tag_youtube')); } PK'9\ tag_youtube/db/access.phpnu[. /** * Tag youtube block caps. * * @package block_tag_youtube * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/tag_youtube:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\ [aatag_youtube/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the tag_youtube block * * @package block_tag_youtube * @copyright 2020 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade code for the Tag Youtube block. * * @param int $oldversion */ function xmldb_block_tag_youtube_upgrade($oldversion) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\VdU )tag_youtube/lang/en/block_tag_youtube.phpnu[. /** * Strings for component 'block_tag_youtube', language 'en', branch 'MOODLE_20_STABLE' * * @package block_tag_youtube * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['anycategory'] = 'Any category'; $string['apierror'] = 'The YouTube API key is not set. Contact your administrator.'; $string['apikey'] = 'API key'; $string['apikeyinfo'] = 'Get a Google API key for your Moodle site.'; $string['autosvehicles'] = 'Autos & Vehicles'; $string['category'] = 'Category'; $string['comedy'] = 'Comedy'; $string['configtitle'] = 'YouTube block title'; $string['education'] = 'Education'; $string['entertainment'] = 'Entertainment'; $string['categoryerror'] = 'Failed to obtain the list of categories.
              {$a}'; $string['filmsanimation'] = 'Films & Animation'; $string['gadgetsgames'] = 'Gadgets & Games'; $string['howtodiy'] = 'How-to & DIY'; $string['includeonlyvideosfromplaylist'] = 'Include only videos from the playlist with id'; $string['music'] = 'Music'; $string['newspolitics'] = 'News & Politics'; $string['nonprofitactivism'] = 'Nonprofits & Activism'; $string['numberofvideos'] = 'Number of videos'; $string['peopleblogs'] = 'People & Blogs'; $string['petsanimals'] = 'Pets & Animals'; $string['pluginname'] = 'YouTube'; $string['requesterror'] = 'Data could not be obtained from the server. Contact your administrator if the problem persists.'; $string['scienceandtech'] = 'Science & Tech'; $string['sports'] = 'Sports'; $string['tag_youtube:addinstance'] = 'Add a new YouTube block'; $string['travel'] = 'Travel & Places'; $string['privacy:metadata'] = 'The YouTube block only shows data stored in other locations.'; PK'9\邤 tag_youtube/edit_form.phpnu[. /** * Form for editing tag_youtube block instances. * * @package block_tag_youtube * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Form for editing tag_youtube block instances. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_tag_youtube_edit_form extends block_edit_form { protected function specific_definition($mform) { global $OUTPUT; $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $mform->addElement('text', 'config_title', get_string('configtitle', 'block_tag_youtube')); $mform->setType('config_title', PARAM_TEXT); $mform->addElement('text', 'config_numberofvideos', get_string('numberofvideos', 'block_tag_youtube'), array('size' => 5)); $mform->setType('config_numberofvideos', PARAM_INT); // Category setting. $categorychoices = ['0' => get_string('anycategory', 'block_tag_youtube')]; $hascategoryerror = false; $categoryerror = ''; try { // Get all video categories through an API call and add them to the category list. $categorychoices += $this->block->get_categories(); } catch (Exception $e) { $hascategoryerror = true; $categoryerror = $e->getMessage(); } $mform->addElement('select', 'config_category', get_string('category', 'block_tag_youtube'), $categorychoices); $mform->setDefault('config_category', 0); if ($hascategoryerror) { $notification = $OUTPUT->notification(get_string('categoryerror', 'block_tag_youtube', $categoryerror), 'error'); $mform->addElement('static', 'config_category_error', '', $notification); } $mform->addElement('text', 'config_playlist', get_string('includeonlyvideosfromplaylist', 'block_tag_youtube')); $mform->setType('config_playlist', PARAM_ALPHANUM); } } PK'9\~tag_youtube/version.phpnu[. /** * Version details * * @package block_tag_youtube * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_tag_youtube'; // Full name of the plugin (used for diagnostics) PK'9\Qpp\9\9!tag_youtube/block_tag_youtube.phpnu[. /** * Tag youtube block * * @package block_tag_youtube * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define('DEFAULT_NUMBER_OF_VIDEOS', 5); class block_tag_youtube extends block_base { /** * @var Google_Service_Youtube */ protected $service = null; function init() { $this->title = get_string('pluginname','block_tag_youtube'); $this->config = new stdClass(); } function applicable_formats() { return array('tag' => true); } /** * It can be configured. * * @return bool */ public function has_config() { return true; } function specialization() { $this->title = !empty($this->config->title) ? $this->config->title : get_string('pluginname', 'block_tag_youtube'); } function instance_allow_multiple() { return true; } function get_content() { global $CFG; //note: do NOT include files at the top of this file require_once($CFG->libdir . '/filelib.php'); if ($this->content !== NULL) { return $this->content; } $this->content = new stdClass(); $this->content->footer = ''; if (!$this->get_service()) { $this->content->text = $this->get_error_message(); return $this->content; } $text = ''; if(!empty($this->config->playlist)){ //videos from a playlist $text = $this->get_videos_by_playlist(); } else{ if(!empty($this->config->category)){ //videos from category with tag $text = $this->get_videos_by_tag_and_category(); } else { //videos with tag $text = $this->get_videos_by_tag(); } } $this->content->text = $text; return $this->content; } function get_videos_by_playlist(){ if (!$service = $this->get_service()) { return $this->get_error_message(); } $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS; if( !empty($this->config->numberofvideos)) { $numberofvideos = $this->config->numberofvideos; } try { $response = $service->playlistItems->listPlaylistItems('id,snippet', array( 'playlistId' => $this->config->playlist, 'maxResults' => $numberofvideos )); } catch (Google_Service_Exception $e) { debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER); return $this->get_error_message(get_string('requesterror', 'block_tag_youtube')); } return $this->render_items($response); } function get_videos_by_tag(){ if (!$service = $this->get_service()) { return $this->get_error_message(); } $tagid = optional_param('id', 0, PARAM_INT); // tag id - for backware compatibility $tag = optional_param('tag', '', PARAM_TAG); // tag $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id. if ($tagid) { $tagobject = core_tag_tag::get($tagid); } else if ($tag) { $tagobject = core_tag_tag::get_by_name($tc, $tag); } if (empty($tagobject)) { return ''; } $querytag = urlencode($tagobject->name); $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS; if ( !empty($this->config->numberofvideos) ) { $numberofvideos = $this->config->numberofvideos; } try { $response = $service->search->listSearch('id,snippet', array( 'q' => $querytag, 'type' => 'video', 'maxResults' => $numberofvideos )); } catch (Google_Service_Exception $e) { debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER); return $this->get_error_message(get_string('requesterror', 'block_tag_youtube')); } return $this->render_items($response); } function get_videos_by_tag_and_category(){ if (!$service = $this->get_service()) { return $this->get_error_message(); } $tagid = optional_param('id', 0, PARAM_INT); // tag id - for backware compatibility $tag = optional_param('tag', '', PARAM_TAG); // tag $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id. if ($tagid) { $tagobject = core_tag_tag::get($tagid); } else if ($tag) { $tagobject = core_tag_tag::get_by_name($tc, $tag); } if (empty($tagobject)) { return ''; } $querytag = urlencode($tagobject->name); $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS; if( !empty($this->config->numberofvideos)) { $numberofvideos = $this->config->numberofvideos; } try { $response = $service->search->listSearch('id,snippet', array( 'q' => $querytag, 'type' => 'video', 'maxResults' => $numberofvideos, 'videoCategoryId' => $this->config->category )); } catch (Google_Service_Exception $e) { debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER); return $this->get_error_message(get_string('requesterror', 'block_tag_youtube')); } return $this->render_items($response); } /** * Returns an error message. * * Useful when the block is not properly set or something goes wrong. * * @param string $message The message to display. * @return string HTML */ protected function get_error_message($message = null) { global $OUTPUT; if (empty($message)) { $message = get_string('apierror', 'block_tag_youtube'); } return $OUTPUT->notification($message); } /** * Gets the youtube service object. * * @return Google_Service_YouTube */ protected function get_service() { global $CFG; if (!$apikey = get_config('block_tag_youtube', 'apikey')) { return false; } // Wrapped in an if in case we call different get_videos_* multiple times. if (!isset($this->service)) { require_once($CFG->libdir . '/google/lib.php'); $client = get_google_client(); $client->setDeveloperKey($apikey); $client->setScopes(array(Google_Service_YouTube::YOUTUBE_READONLY)); $this->service = new Google_Service_YouTube($client); } return $this->service; } /** * Renders the list of items. * * @param stdClass $videosdata * @return string HTML */ protected function render_items($videosdata) { if (!$videosdata || empty($videosdata->items)) { if (!empty($videosdata->error)) { debugging('Error fetching data from youtube: ' . $videosdata->error->message, DEBUG_DEVELOPER); } return ''; } // If we reach that point we already know that the API key is set. $service = $this->get_service(); $text = html_writer::start_tag('ul', array('class' => 'yt-video-entry unlist img-text')); foreach ($videosdata->items as $video) { // Link to the video included in the playlist if listing a playlist. if (!empty($video->snippet->resourceId)) { $id = $video->snippet->resourceId->videoId; $playlist = '&list=' . $video->snippet->playlistId; } else { $id = $video->id->videoId; $playlist = ''; } $thumbnail = $video->snippet->getThumbnails()->getDefault(); $url = 'http://www.youtube.com/watch?v=' . $id . $playlist; $videodetails = $service->videos->listVideos('id,contentDetails', array('id' => $id)); if ($videodetails && !empty($videodetails->items)) { // We fetch by id so we just use the first one. $details = $videodetails->items[0]; $start = new DateTime('@0'); $start->add(new DateInterval($details->contentDetails->duration)); $seconds = $start->format('U'); } $text .= html_writer::start_tag('li'); $imgattrs = array('class' => 'youtube-thumb', 'src' => $thumbnail->url, 'alt' => $video->snippet->title); $thumbhtml = html_writer::empty_tag('img', $imgattrs); $link = html_writer::tag('a', $thumbhtml, array('href' => $url)); $text .= html_writer::tag('div', $link, array('class' => 'clearfix')); $text .= html_writer::tag('span', html_writer::tag('a', $video->snippet->title, array('href' => $url))); if (!empty($seconds)) { $text .= html_writer::tag('div', format_time($seconds)); } $text .= html_writer::end_tag('li'); } $text .= html_writer::end_tag('ul'); return $text; } /** * Method that returns an array containing all relevant video categories obtained through an API call, where the * array index represents the category ID and the array value represents the category name. * * @return array The array containing the relevant video categories * @throws moodle_exception If the API key is not set * @throws Google_Service_Exception If an error occurs while obtaining the categories through the API call */ public function get_categories() { // Get the default categories and it's translations. $categorytranslations = $this->category_map_translation(); if ($service = $this->get_service()) { // Call the API to fetch the youtube video categories. // This API call requires the regionCode parameter which instructs the API to return the list of video // categories available in the specified country. Currently 'us' is hardcoded as the returned categories // for this region correspond to the previously used (legacy) hardcoded list of categories. // TODO: We should improve this in the future and avoid hardcoding this value. $response = $service->videoCategories->listVideoCategories('snippet', ['regionCode' => 'us']); $categoryitems = $response['modelData']['items']; // Return an array with the relevant categories. return array_reduce($categoryitems, function($categories, $category) use ($categorytranslations) { $categoryid = $category['id']; $categoryname = $category['snippet']['title']; // Videos can be associated with this category. if ($category['snippet']['assignable']) { // If the category name can be mapped with a translation, add it to the categories array. if (array_key_exists($categoryname, $categorytranslations)) { $categories[$categoryid] = $categorytranslations[$categoryname]; } else { // Otherwise, display the untranslated category name and show a debugging message. $categories[$categoryid] = $categoryname; debugging("The category '{$categoryname}' does not have a translatable language string."); } } return $categories; }, []); } else { throw new \moodle_exception('apierror', 'block_tag_youtube'); } } /** * Method that provides mapping between the video category names and their translations. * * @return array The array that maps the video category names with their translations */ private function category_map_translation() { return [ 'Film & Animation' => get_string('filmsanimation', 'block_tag_youtube'), 'Autos & Vehicles' => get_string('autosvehicles', 'block_tag_youtube'), 'Music' => get_string('music', 'block_tag_youtube'), 'Pets & Animals' => get_string('petsanimals', 'block_tag_youtube'), 'Sports' => get_string('sports', 'block_tag_youtube'), 'Travel & Events' => get_string('travel', 'block_tag_youtube'), 'Gaming' => get_string('gadgetsgames', 'block_tag_youtube'), 'People & Blogs' => get_string('peopleblogs', 'block_tag_youtube'), 'Comedy' => get_string('comedy', 'block_tag_youtube'), 'Entertainment' => get_string('entertainment', 'block_tag_youtube'), 'News & Politics' => get_string('newspolitics', 'block_tag_youtube'), 'Howto & Style' => get_string('howtodiy', 'block_tag_youtube'), 'Education' => get_string('education', 'block_tag_youtube'), 'Science & Technology' => get_string('scienceandtech', 'block_tag_youtube'), 'Nonprofits & Activism' => get_string('nonprofitactivism', 'block_tag_youtube'), ]; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // There is a private key, only admins can see it. $pluginconfigs = get_config('block_tag_youtube'); if (!has_capability('moodle/site:config', context_system::instance())) { unset($pluginconfigs->apikey); } $instanceconfigs = !empty($this->config) ? $this->config : new stdClass(); return (object) [ 'instance' => $instanceconfigs, 'plugin' => $pluginconfigs, ]; } } PK'9\(tag_youtube/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_tag_youtube. * * @package block_tag_youtube * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_tag_youtube\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_tag_youtube implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\,tag_youtube/upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This files describes API changes in the block tag_youtube code. === 3.10.1 === * The config category now stores the category ID, instead of a string representation of the category name. In YouTube Data API v3, the API call to fetch the videos related to a certain category expects the category ID to be passed to the videoCategoryId parameter, instead of the category name. === 3.0 === * Due to the final YouTube API v2.0 deprecation we needed to adapt the current code to YouTube Data API v3. block_tag_youtube::fetch_request and block_tag_youtube::render_video_list have been deprecated as they can not be used any more. PK'9\.,tag_youtube/tests/block_tag_youtube_test.phpnu[. namespace block_tag_youtube; /** * Block Tag Youtube test class. * * @package block_tag_youtube * @category test * @copyright 2015 Jun Pataleta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class block_tag_youtube_test extends \advanced_testcase { /** * Testing the tag youtube block's initial state after a new installation. * * @return void */ public function test_after_install(): void { global $DB; $this->resetAfterTest(true); // Assert that tag_youtube entry exists and that its visible attribute is set to 0 (disabled). $this->assertTrue($DB->record_exists('block', array('name' => 'tag_youtube', 'visible' => 0))); } } PK'9\  ;tag_youtube/tests/behat/configure_tag_youtube_block.featurenu[@block @block_tag_youtube Feature: Adding and configuring YouTube block In order to have the YouTube block used As a admin I need to add the YouTube block to the tags site page Background: Given I log in as "admin" And I navigate to "Plugins > Blocks > Manage blocks" in site administration And I toggle the "Enable YouTube" admin switch "on" @javascript Scenario: Category options are not available (except default) in the block settings if the YouTube API key is not set. Given the following config values are set as admin: | apikey | | block_tag_youtube | And I follow "Dashboard" And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost | # TODO MDL-57120 site "Tags" link not accessible without navigation block. And I add the "Navigation" block if not present And I click on "Site pages" "list_item" in the "Navigation" "block" And I click on "Tags" "link" in the "Navigation" "block" And I add the "YouTube" block When I configure the "YouTube" block Then I should see "Category" And I should see "Failed to obtain the list of categories." And I should see "The YouTube API key is not set. Contact your administrator." And the "Category" select box should contain "Any category" And the "Category" select box should not contain "Films & Animation" And the "Category" select box should not contain "Entertainment" And the "Category" select box should not contain "Education" @javascript Scenario: Category options are not available (except default) in the block settings when invalid YouTube API key is set. Given the following config values are set as admin: | apikey | invalidapikeyvalue | block_tag_youtube | And I follow "Dashboard" And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost | And I add the "Navigation" block if not present And I click on "Site pages" "list_item" in the "Navigation" "block" And I click on "Tags" "link" in the "Navigation" "block" And I add the "YouTube" block When I configure the "YouTube" block Then I should see "Category" And I should see "Failed to obtain the list of categories." And the "Category" select box should contain "Any category" And the "Category" select box should not contain "Comedy" And the "Category" select box should not contain "Autos & Vehicles" And the "Category" select box should not contain "News & Politics" PK'9\\.tag_youtube/settings.phpnu[. /** * Settings for the RSS client block. * * @package block_tag_youtube * @copyright 2015 David Monllao * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { $settings->add(new admin_setting_configtext('block_tag_youtube/apikey', get_string('apikey', 'block_tag_youtube'), get_string('apikeyinfo', 'block_tag_youtube'), '', PARAM_RAW_TRIMMED, 40)); } PK'9\2 index.htmlnu[ PK'9\yA%%calendar_month/db/access.phpnu[. /** * Calendar month block caps. * * @package block_calendar_month * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/calendar_month:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/calendar_month:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\--calendar_month/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the calendar_month block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @since Moodle 2.8 * @package block_calendar_month * @copyright 2014 Andrew Davis * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade the calendar_month block * @param int $oldversion * @param object $block */ function xmldb_block_calendar_month_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\?/calendar_month/lang/en/block_calendar_month.phpnu[. /** * Strings for component 'block_calendar_month', language 'en', branch 'MOODLE_20_STABLE' * * @package block_calendar_month * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['calendar_month:addinstance'] = 'Add a new calendar block'; $string['calendar_month:myaddinstance'] = 'Add a new calendar block to Dashboard'; $string['pluginname'] = 'Calendar'; $string['privacy:metadata'] = 'The Calendar block only displays existing calendar data.'; PK'9\^~4Ɋ 'calendar_month/block_calendar_month.phpnu[. /** * Handles displaying the calendar block. * * @package block_calendar_month * @copyright 2004 Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_calendar_month extends block_base { /** * Initialise the block. */ public function init() { $this->title = get_string('pluginname', 'block_calendar_month'); } /** * Return the content of this block. * * @return stdClass the content */ public function get_content() { global $CFG; require_once($CFG->dirroot.'/calendar/lib.php'); if ($this->content !== null) { return $this->content; } $this->content = new stdClass; $this->content->footer = ''; $renderer = $this->page->get_renderer('core_calendar'); $this->content->text = $renderer->start_layout(); $courseid = $this->page->course->id; $categoryid = ($this->page->context->contextlevel === CONTEXT_COURSECAT && !empty($this->page->category)) ? $this->page->category->id : null; $calendar = \calendar_information::create(time(), $courseid, $categoryid); list($data, $template) = calendar_get_view($calendar, 'monthblock', isloggedin()); // Add a flag that this is coming from calendar block. $data->iscalendarblock = true; $renderer = $this->page->get_renderer('core_calendar'); $this->content->text .= $renderer->render_from_template($template, $data); $options = [ 'showfullcalendarlink' => true ]; list($footerdata, $footertemplate) = calendar_get_footer_options($calendar, $options); $this->content->footer .= $renderer->render_from_template($footertemplate, $footerdata); $this->content->text .= $renderer->complete_layout(); $this->page->requires->js_call_amd('core_calendar/popover'); return $this->content; } } PK'9\calendar_month/version.phpnu[. /** * Version details * * @package block_calendar_month * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_calendar_month'; // Full name of the plugin (used for diagnostics) PK'9\ 6+calendar_month/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_calendar_month. * * @package block_calendar_month * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_calendar_month\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_calendar_month implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\vؒ  7calendar_month/tests/behat/block_calendar_month.featurenu[@block @block_calendar_month Feature: Enable the calendar block in a course and test it's functionality In order to enable the calendar block in a course As a teacher I can add the calendar block to a course Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | Scenario: Add the block to a the course Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Calendar" block Then "Calendar" "block" should exist @javascript Scenario: View a site event in the calendar block Given I log in as "admin" And I create a calendar event with form data: | id_eventtype | Site | | id_name | Site Event | And I log out When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Calendar" block And I hover over today in the mini-calendar block Then I should see "Site Event" @javascript Scenario: View a course event in the calendar block Given I log in as "teacher1" And I create a calendar event with form data: | Type of event | course | | Course | Course 1 | | Event title | Course Event | When I am on "Course 1" course homepage with editing mode on And I add the "Calendar" block And I hover over today in the mini-calendar block Then I should see "Course Event" @javascript Scenario: View a user event in the calendar block Given the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | calendar_month | Course | C1 | course-view-* | side-pre | And I log in as "teacher1" And I create a calendar event with form data: | id_eventtype | User | | id_name | User Event | When I am on "Course 1" course homepage And I hover over today in the mini-calendar block Then I should see "User Event" @javascript Scenario: View a group event in the calendar block Given the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | | Group 2 | C1 | G2 | And the following "group members" exist: | user | group | | student1 | G1 | | student2 | G2 | When I am on the "Course 1" "course editing" page logged in as teacher1 And I set the following fields to these values: | id_groupmode | Separate groups | | id_groupmodeforce | Yes | And I press "Save and display" And I turn editing mode on And I add the "Calendar" block And I click on "Course calendar" "link" And I create a calendar event: | Type of event | group | | Group | Group 1 | | Event title | Group Event | And I am on the "Course 1" course page logged in as student1 And I hover over today in the mini-calendar block Then I should see "Group Event" And I am on the "Course 1" course page logged in as student2 And I should not see "Group Event" @javascript Scenario: Click on today's course event on the calendar view page's calendar block Given I log in as "admin" And I create a calendar event with form data: | id_eventtype | Site | | id_name | Site Event | And I am on site homepage And I turn editing mode on And I add the "Calendar" block And I configure the "Calendar" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" When I am on "Course 1" course homepage And I follow "Course calendar" And I click on today in the mini-calendar block to view the detail Then I should see "Site Event" in the "Calendar" "block" And ".popover" "css_element" should not exist PK'9\Gi  >calendar_month/tests/behat/block_calendar_month_course.featurenu[@block @block_calendar_month Feature: Enable the calendar block in a course In order to enable the calendar block in a course As a teacher I can add the calendar block to a course @javascript Scenario: View a site event in the calendar block in a course Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | When I log in as "admin" And I create a calendar event with form data: | id_eventtype | Site | | id_name | Site Event | And I log out Then I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Calendar" block And I hover over today in the mini-calendar block And I should see "Site Event" PK'9\WΊAcalendar_month/tests/behat/block_calendar_month_dashboard.featurenu[@block @block_calendar_month Feature: View a site event on the dashboard In order to view a site event As a student I can view the event in the calendar Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | And I log in as "admin" And I create a calendar event with form data: | id_eventtype | Site | | id_name | Site Event | And I log out @javascript Scenario: View a site event in the calendar block on the dashboard Given I log in as "student1" When I hover over today in the mini-calendar block Then I should see "Site Event" @javascript Scenario: The calendar block on the dashboard should be responsive Given I log in as "student1" When I change viewport size to "1200x1000" Then I should see "Site Event" And I change viewport size to "600x1000" # We need to give the browser a couple seconds to re-render the page after the screen has been resized. And I wait "1" seconds And I should not see "Site Event" When I hover over today in the mini-calendar block responsive view And I should see "Site Event" PK'9\>OAcalendar_month/tests/behat/block_calendar_month_frontpage.featurenu[@block @block_calendar_month Feature: Enable the calendar block on the site front page In order to enable the calendar block on the site front page As an admin I can add the calendar block on the site front page @javascript Scenario: View a site event in the calendar block on the front page Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | calendar_month | System | 1 | site-index | side-pre | And I log in as "admin" And I create a calendar event with form data: | id_eventtype | Site | | id_name | Site Event | And I log out When I log in as "student1" And I am on site homepage And I hover over today in the mini-calendar block Then I should see "Site Event" PK'9\myoverview/styles.cssnu[/* Hide the first dropdown-divider if no filter option element is listed before it. This can happen for some subset configurations of the block_myoverview course filter. */ .block_myoverview button#groupingdropdown + .dropdown-menu li:first-of-type.dropdown-divider:first-of-type { display: none; } .block_myoverview .whitebutton .btn-secondary { background: white; border-color: var(--bs-primary); color: var(--bs-primary); } .page-mycourses .block_myoverview .block-controls { display: none; } PK'9\ѢOmyoverview/db/access.phpnu[. /** * Capabilities for the My overview block. * * @package block_myoverview * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/myoverview:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ) ); PK'9\o`oomyoverview/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the myoverview block * * @since 3.8 * @package block_myoverview * @copyright 2019 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade code for the MyOverview block. * * @param int $oldversion */ function xmldb_block_myoverview_upgrade($oldversion) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\[myoverview/block_myoverview.phpnu[. /** * Contains the class for the My overview block. * * @package block_myoverview * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * My overview block class. * * @package block_myoverview * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_myoverview extends block_base { /** * Init. */ public function init() { $this->title = get_string('pluginname', 'block_myoverview'); } /** * Returns the contents. * * @return stdClass contents of block */ public function get_content() { if (isset($this->content)) { return $this->content; } $group = get_user_preferences('block_myoverview_user_grouping_preference'); $sort = get_user_preferences('block_myoverview_user_sort_preference'); $view = get_user_preferences('block_myoverview_user_view_preference'); $paging = get_user_preferences('block_myoverview_user_paging_preference'); $customfieldvalue = get_user_preferences('block_myoverview_user_grouping_customfieldvalue_preference'); $renderable = new \block_myoverview\output\main($group, $sort, $view, $paging, $customfieldvalue); $renderer = $this->page->get_renderer('block_myoverview'); $this->content = new stdClass(); $this->content->text = $renderer->render($renderable); $this->content->footer = ''; return $this->content; } /** * Locations where block can be displayed. * * @return array */ public function applicable_formats() { return array('my' => true); } /** * Allow the block to have a configuration page. * * @return boolean */ public function has_config() { return true; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = get_config('block_myoverview'); // Get the customfield values (if any). if ($configs->displaygroupingcustomfield) { $group = get_user_preferences('block_myoverview_user_grouping_preference'); $sort = get_user_preferences('block_myoverview_user_sort_preference'); $view = get_user_preferences('block_myoverview_user_view_preference'); $paging = get_user_preferences('block_myoverview_user_paging_preference'); $customfieldvalue = get_user_preferences('block_myoverview_user_grouping_customfieldvalue_preference'); $renderable = new \block_myoverview\output\main($group, $sort, $view, $paging, $customfieldvalue); $customfieldsexport = $renderable->get_customfield_values_for_export(); if (!empty($customfieldsexport)) { $configs->customfieldsexport = json_encode($customfieldsexport); } } return (object) [ 'instance' => new stdClass(), 'plugin' => $configs, ]; } /** * Disable block editing on the my courses page. * * @return boolean */ public function instance_can_be_edited() { if ($this->page->blocks->is_known_region(BLOCK_POS_LEFT) || $this->page->blocks->is_known_region(BLOCK_POS_RIGHT)) { return true; } else { return false; } } /** * Hide the block header on the my courses page. * * @return boolean */ public function hide_header() { if ($this->page->blocks->is_known_region(BLOCK_POS_LEFT) || $this->page->blocks->is_known_region(BLOCK_POS_RIGHT)) { return false; } else { return true; } } } PK'9\eI~~'myoverview/lang/en/block_myoverview.phpnu[. /** * Lang strings for the My overview block. * * @package block_myoverview * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['allincludinghidden'] = 'All (including removed from view)'; $string['all'] = 'All'; $string['addtofavourites'] = 'Star this course'; $string['aria:addtofavourites'] = 'Star for'; $string['aria:allcoursesincludinghidden'] = 'Show all courses'; $string['aria:allcourses'] = 'Show all courses except courses removed from view'; $string['aria:card'] = 'Switch to card view'; $string['aria:controls'] = 'Course overview controls'; $string['aria:courseactions'] = 'Actions for course'; $string['aria:coursesummary'] = 'Course summary text:'; $string['aria:courseprogress'] = 'Course progress:'; $string['aria:customfield'] = 'Show {$a} courses'; $string['aria:displaydropdown'] = 'Display drop-down menu'; $string['aria:favourites'] = 'Show starred courses only'; $string['aria:future'] = 'Show future courses'; $string['aria:groupingdropdown'] = 'Grouping drop-down menu'; $string['aria:inprogress'] = 'Show courses in progress'; $string['aria:list'] = 'Switch to list view'; $string['aria:past'] = 'Show past courses'; $string['aria:removefromfavourites'] = 'Remove star for'; $string['aria:summary'] = 'Switch to summary view'; $string['aria:sortingdropdown'] = 'Sorting drop-down menu'; $string['availablegroupings'] = 'Available filters'; $string['availablegroupings_desc'] = 'Course filters which are available for selection by users. If none are selected, all courses will be displayed.'; $string['card'] = 'Card'; $string['cards'] = 'Cards'; $string['courseprogress'] = 'Course progress:'; $string['completepercent'] = '{$a}% complete'; $string['createcourse'] = 'Create course'; $string['customfield'] = 'Custom field'; $string['customfiltergrouping'] = 'Field to use'; $string['customfiltergrouping_nofields'] = 'This option requires a course custom field to be set up and visible to everyone.'; $string['displaycategories'] = 'Display categories'; $string['displaycategories_help'] = 'Display the course category on dashboard course items including cards, list items and summary items.'; $string['favourites'] = 'Starred'; $string['future'] = 'Future'; $string['inprogress'] = 'In progress'; $string['lastaccessed'] = 'Last accessed'; $string['layouts'] = 'Available layouts'; $string['layouts_help'] = 'Course overview layouts which are available for selection by users. If none are selected, the card layout will be used.'; $string['list'] = 'List'; $string['myoverview:myaddinstance'] = 'Add a new course overview block to Dashboard'; $string['nocustomvalue'] = 'No {$a}'; $string['past'] = 'Past'; $string['pluginname'] = 'Course overview'; $string['privacy:metadata:overviewsortpreference'] = 'The Course overview block sort preference.'; $string['privacy:metadata:overviewviewpreference'] = 'The Course overview block view preference.'; $string['privacy:metadata:overviewgroupingpreference'] = 'The Course overview block grouping preference.'; $string['privacy:metadata:overviewpagingpreference'] = 'The Course overview block paging preference.'; $string['removefromfavourites'] = 'Unstar this course'; $string['searchcourses'] = "Search courses"; $string['shortname'] = 'Short name'; $string['summary'] = 'Summary'; $string['title'] = 'Course name'; $string['aria:hidecourse'] = 'Remove {$a} from view'; $string['aria:showcourse'] = 'Restore {$a} to view'; $string['aria:hiddencourses'] = 'Show courses removed from view'; $string['hidden'] = 'Courses removed from view'; $string['hidecourse'] = 'Remove from view'; $string['hiddencourses'] = 'Removed from view'; $string['show'] = 'Restore to view'; $string['sortbytitle'] = 'Sort by course name'; $string['sortbylastaccessed'] = 'Sort by last accessed'; $string['sortbyshortname'] = 'Sort by short name'; $string['privacy:request:preference:set'] = 'The value of the setting \'{$a->name}\' was \'{$a->value}\''; $string['viewquickstart'] = 'View Quickstart guide'; $string['zero_default_title'] = 'You\'re not enrolled in any courses.'; $string['zero_default_intro'] = 'Once you\'re enrolled in a course, it will appear here.'; $string['zero_nocourses_intro'] = 'Need help getting started? Check out the Moodle documentation.'; $string['zero_nocourses_title'] = 'Create your first course'; $string['zero_request_title'] = 'Request your first course'; $string['zero_request_intro'] = 'Need help getting started? Check out the Moodle documentation or take your first steps with our Quickstart guide.'; PK'9\Fmyoverview/pix/courses.svgnu[ PK'9\GyPmyoverview/lib.phpnu[. /** * Library functions for overview. * * @package block_myoverview * @copyright 2018 Peter Dias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Constants for the user preferences grouping options */ define('BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN', 'allincludinghidden'); define('BLOCK_MYOVERVIEW_GROUPING_ALL', 'all'); define('BLOCK_MYOVERVIEW_GROUPING_INPROGRESS', 'inprogress'); define('BLOCK_MYOVERVIEW_GROUPING_FUTURE', 'future'); define('BLOCK_MYOVERVIEW_GROUPING_PAST', 'past'); define('BLOCK_MYOVERVIEW_GROUPING_FAVOURITES', 'favourites'); define('BLOCK_MYOVERVIEW_GROUPING_HIDDEN', 'hidden'); define('BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD', 'customfield'); /** * Allows selection of all courses without a value for the custom field. */ define('BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY', -1); /** * Constants for the user preferences sorting options * timeline */ define('BLOCK_MYOVERVIEW_SORTING_TITLE', 'title'); define('BLOCK_MYOVERVIEW_SORTING_LASTACCESSED', 'lastaccessed'); define('BLOCK_MYOVERVIEW_SORTING_SHORTNAME', 'shortname'); /** * Constants for the user preferences view options */ define('BLOCK_MYOVERVIEW_VIEW_CARD', 'card'); define('BLOCK_MYOVERVIEW_VIEW_LIST', 'list'); define('BLOCK_MYOVERVIEW_VIEW_SUMMARY', 'summary'); /** * Constants for the user paging preferences */ define('BLOCK_MYOVERVIEW_PAGING_12', 12); define('BLOCK_MYOVERVIEW_PAGING_24', 24); define('BLOCK_MYOVERVIEW_PAGING_48', 48); define('BLOCK_MYOVERVIEW_PAGING_96', 96); define('BLOCK_MYOVERVIEW_PAGING_ALL', 0); /** * Constants for the admin category display setting */ define('BLOCK_MYOVERVIEW_DISPLAY_CATEGORIES_ON', 'on'); define('BLOCK_MYOVERVIEW_DISPLAY_CATEGORIES_OFF', 'off'); /** * Get the current user preferences that are available * * @uses core_user::is_current_user * * @return array[] Array representing current options along with defaults */ function block_myoverview_user_preferences(): array { $preferences['block_myoverview_user_grouping_preference'] = array( 'null' => NULL_NOT_ALLOWED, 'default' => BLOCK_MYOVERVIEW_GROUPING_ALL, 'type' => PARAM_ALPHA, 'choices' => array( BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN, BLOCK_MYOVERVIEW_GROUPING_ALL, BLOCK_MYOVERVIEW_GROUPING_INPROGRESS, BLOCK_MYOVERVIEW_GROUPING_FUTURE, BLOCK_MYOVERVIEW_GROUPING_PAST, BLOCK_MYOVERVIEW_GROUPING_FAVOURITES, BLOCK_MYOVERVIEW_GROUPING_HIDDEN, BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD, ), 'permissioncallback' => [core_user::class, 'is_current_user'], ); $preferences['block_myoverview_user_grouping_customfieldvalue_preference'] = [ 'null' => NULL_ALLOWED, 'default' => null, 'type' => PARAM_RAW, 'permissioncallback' => [core_user::class, 'is_current_user'], ]; $preferences['block_myoverview_user_sort_preference'] = array( 'null' => NULL_NOT_ALLOWED, 'default' => BLOCK_MYOVERVIEW_SORTING_LASTACCESSED, 'type' => PARAM_ALPHA, 'choices' => array( BLOCK_MYOVERVIEW_SORTING_TITLE, BLOCK_MYOVERVIEW_SORTING_LASTACCESSED, BLOCK_MYOVERVIEW_SORTING_SHORTNAME ), 'permissioncallback' => [core_user::class, 'is_current_user'], ); $preferences['block_myoverview_user_view_preference'] = array( 'null' => NULL_NOT_ALLOWED, 'default' => BLOCK_MYOVERVIEW_VIEW_CARD, 'type' => PARAM_ALPHA, 'choices' => array( BLOCK_MYOVERVIEW_VIEW_CARD, BLOCK_MYOVERVIEW_VIEW_LIST, BLOCK_MYOVERVIEW_VIEW_SUMMARY ), 'permissioncallback' => [core_user::class, 'is_current_user'], ); $preferences['/^block_myoverview_hidden_course_(\d)+$/'] = array( 'isregex' => true, 'choices' => array(0, 1), 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'permissioncallback' => [core_user::class, 'is_current_user'], ); $preferences['block_myoverview_user_paging_preference'] = array( 'null' => NULL_NOT_ALLOWED, 'default' => BLOCK_MYOVERVIEW_PAGING_12, 'type' => PARAM_INT, 'choices' => array( BLOCK_MYOVERVIEW_PAGING_12, BLOCK_MYOVERVIEW_PAGING_24, BLOCK_MYOVERVIEW_PAGING_48, BLOCK_MYOVERVIEW_PAGING_96, BLOCK_MYOVERVIEW_PAGING_ALL ), 'permissioncallback' => [core_user::class, 'is_current_user'], ); return $preferences; } /** * Pre-delete course hook to cleanup any records with references to the deleted course. * * @param stdClass $course The deleted course */ function block_myoverview_pre_course_delete(\stdClass $course) { // Removing any favourited courses which have been created for users, for this course. $service = \core_favourites\service_factory::get_service_for_component('core_course'); $service->delete_favourites_by_type_and_item('courses', $course->id); } PK'9\b)Mmyoverview/version.phpnu[. /** * Version details for the My overview block. * * @package block_myoverview * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_myoverview'; // Full name of the plugin (used for diagnostics). PK'9\W'myoverview/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_myoverview. * * @package block_myoverview * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_myoverview\privacy; use core_privacy\local\request\user_preference_provider; use core_privacy\local\metadata\collection; use \core_privacy\local\request\writer; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_myoverview. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, user_preference_provider { /** * Returns meta-data information about the myoverview block. * * @param \core_privacy\local\metadata\collection $collection A collection of meta-data. * @return \core_privacy\local\metadata\collection Return the collection of meta-data. */ public static function get_metadata(collection $collection): collection { $collection->add_user_preference('block_myoverview_user_sort_preference', 'privacy:metadata:overviewsortpreference'); $collection->add_user_preference('block_myoverview_user_view_preference', 'privacy:metadata:overviewviewpreference'); $collection->add_user_preference('block_myoverview_user_grouping_preference', 'privacy:metadata:overviewgroupingpreference'); $collection->add_user_preference('block_myoverview_user_paging_preference', 'privacy:metadata:overviewpagingpreference'); return $collection; } /** * Export all user preferences for the myoverview block * * @param int $userid The userid of the user whose data is to be exported. */ public static function export_user_preferences(int $userid) { $preference = get_user_preferences('block_myoverview_user_sort_preference', null, $userid); if (isset($preference)) { writer::export_user_preference('block_myoverview', 'block_myoverview_user_sort_preference', get_string($preference, 'block_myoverview'), get_string('privacy:metadata:overviewsortpreference', 'block_myoverview')); } $preference = get_user_preferences('block_myoverview_user_view_preference', null, $userid); if (isset($preference)) { writer::export_user_preference('block_myoverview', 'block_myoverview_user_view_preference', get_string($preference, 'block_myoverview'), get_string('privacy:metadata:overviewviewpreference', 'block_myoverview')); } $preference = get_user_preferences('block_myoverview_user_grouping_preference', null, $userid); if (isset($preference)) { writer::export_user_preference('block_myoverview', 'block_myoverview_user_grouping_preference', get_string($preference, 'block_myoverview'), get_string('privacy:metadata:overviewgroupingpreference', 'block_myoverview')); } $preferences = get_user_preferences(null, null, $userid); foreach ($preferences as $name => $value) { if ((substr($name, 0, 30) == 'block_myoverview_hidden_course')) { writer::export_user_preference( 'block_myoverview', $name, $value, get_string('privacy:request:preference:set', 'block_myoverview', (object) [ 'name' => $name, 'value' => $value, ]) ); } } $preference = get_user_preferences('block_myoverview_user_paging_preference', null, $userid); if (isset($preference)) { \core_privacy\local\request\writer::export_user_preference('block_myoverview', 'block_myoverview_user_paging_preference', $preference, get_string('privacy:metadata:overviewpagingpreference', 'block_myoverview')); } } }PK'9\=\\"myoverview/classes/output/main.phpnu[. /** * Class containing data for my overview block. * * @package block_myoverview * @copyright 2017 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_myoverview\output; defined('MOODLE_INTERNAL') || die(); use core_competency\url; use renderable; use renderer_base; use templatable; use stdClass; require_once($CFG->dirroot . '/blocks/myoverview/lib.php'); /** * Class containing data for my overview block. * * @copyright 2018 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class main implements renderable, templatable { /** * Store the grouping preference. * * @var string String matching the grouping constants defined in myoverview/lib.php */ private $grouping; /** * Store the sort preference. * * @var string String matching the sort constants defined in myoverview/lib.php */ private $sort; /** * Store the view preference. * * @var string String matching the view/display constants defined in myoverview/lib.php */ private $view; /** * Store the paging preference. * * @var string String matching the paging constants defined in myoverview/lib.php */ private $paging; /** * Store the display categories config setting. * * @var boolean */ private $displaycategories; /** * Store the configuration values for the myoverview block. * * @var array Array of available layouts matching view/display constants defined in myoverview/lib.php */ private $layouts; /** * Store a course grouping option setting * * @var boolean */ private $displaygroupingallincludinghidden; /** * Store a course grouping option setting. * * @var boolean */ private $displaygroupingall; /** * Store a course grouping option setting. * * @var boolean */ private $displaygroupinginprogress; /** * Store a course grouping option setting. * * @var boolean */ private $displaygroupingfuture; /** * Store a course grouping option setting. * * @var boolean */ private $displaygroupingpast; /** * Store a course grouping option setting. * * @var boolean */ private $displaygroupingfavourites; /** * Store a course grouping option setting. * * @var boolean */ private $displaygroupinghidden; /** * Store a course grouping option setting. * * @var bool */ private $displaygroupingcustomfield; /** * Store the custom field used by customfield grouping. * * @var string */ private $customfiltergrouping; /** * Store the selected custom field value to group by. * * @var string */ private $customfieldvalue; /** @var bool true if grouping selector should be shown, otherwise false. */ protected $displaygroupingselector; /** * main constructor. * Initialize the user preferences * * @param string $grouping Grouping user preference * @param string $sort Sort user preference * @param string $view Display user preference * @param int $paging * @param string $customfieldvalue * * @throws \dml_exception */ public function __construct($grouping, $sort, $view, $paging, $customfieldvalue = null) { global $CFG; // Get plugin config. $config = get_config('block_myoverview'); // Build the course grouping option name to check if the given grouping is enabled afterwards. $groupingconfigname = 'displaygrouping'.$grouping; // Check the given grouping and remember it if it is enabled. if ($grouping && $config->$groupingconfigname == true) { $this->grouping = $grouping; // Otherwise fall back to another grouping in a reasonable order. // This is done to prevent one-time UI glitches in the case when a user has chosen a grouping option previously which // was then disabled by the admin in the meantime. } else { $this->grouping = $this->get_fallback_grouping($config); } unset ($groupingconfigname); // Remember which custom field value we were using, if grouping by custom field. $this->customfieldvalue = $customfieldvalue; // Check and remember the given sorting. if ($sort) { $this->sort = $sort; } else if ($CFG->courselistshortnames) { $this->sort = BLOCK_MYOVERVIEW_SORTING_SHORTNAME; } else { $this->sort = BLOCK_MYOVERVIEW_SORTING_TITLE; } // In case sorting remembered is shortname and display extended course names not checked, // we should revert sorting to title. if (!$CFG->courselistshortnames && $sort == BLOCK_MYOVERVIEW_SORTING_SHORTNAME) { $this->sort = BLOCK_MYOVERVIEW_SORTING_TITLE; } // Check and remember the given view. $this->view = $view ? $view : BLOCK_MYOVERVIEW_VIEW_CARD; // Check and remember the given page size, `null` indicates no page size set // while a `0` indicates a paging size of `All`. if (!is_null($paging) && $paging == BLOCK_MYOVERVIEW_PAGING_ALL) { $this->paging = BLOCK_MYOVERVIEW_PAGING_ALL; } else { $this->paging = $paging ? $paging : BLOCK_MYOVERVIEW_PAGING_12; } // Check and remember if the course categories should be shown or not. if (!$config->displaycategories) { $this->displaycategories = BLOCK_MYOVERVIEW_DISPLAY_CATEGORIES_OFF; } else { $this->displaycategories = BLOCK_MYOVERVIEW_DISPLAY_CATEGORIES_ON; } // Get and remember the available layouts. $this->set_available_layouts(); $this->view = $view ? $view : reset($this->layouts); // Check and remember if the particular grouping options should be shown or not. $this->displaygroupingallincludinghidden = $config->displaygroupingallincludinghidden; $this->displaygroupingall = $config->displaygroupingall; $this->displaygroupinginprogress = $config->displaygroupinginprogress; $this->displaygroupingfuture = $config->displaygroupingfuture; $this->displaygroupingpast = $config->displaygroupingpast; $this->displaygroupingfavourites = $config->displaygroupingfavourites; $this->displaygroupinghidden = $config->displaygroupinghidden; $this->displaygroupingcustomfield = ($config->displaygroupingcustomfield && $config->customfiltergrouping); $this->customfiltergrouping = $config->customfiltergrouping; // Check and remember if the grouping selector should be shown at all or not. // It will be shown if more than 1 grouping option is enabled. $displaygroupingselectors = array($this->displaygroupingallincludinghidden, $this->displaygroupingall, $this->displaygroupinginprogress, $this->displaygroupingfuture, $this->displaygroupingpast, $this->displaygroupingfavourites, $this->displaygroupinghidden); $displaygroupingselectorscount = count(array_filter($displaygroupingselectors)); if ($displaygroupingselectorscount > 1 || $this->displaygroupingcustomfield) { $this->displaygroupingselector = true; } else { $this->displaygroupingselector = false; } unset ($displaygroupingselectors, $displaygroupingselectorscount); } /** * Determine the most sensible fallback grouping to use (in cases where the stored selection * is no longer available). * @param object $config * @return string */ private function get_fallback_grouping($config) { if ($config->displaygroupingall == true) { return BLOCK_MYOVERVIEW_GROUPING_ALL; } if ($config->displaygroupingallincludinghidden == true) { return BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN; } if ($config->displaygroupinginprogress == true) { return BLOCK_MYOVERVIEW_GROUPING_INPROGRESS; } if ($config->displaygroupingfuture == true) { return BLOCK_MYOVERVIEW_GROUPING_FUTURE; } if ($config->displaygroupingpast == true) { return BLOCK_MYOVERVIEW_GROUPING_PAST; } if ($config->displaygroupingfavourites == true) { return BLOCK_MYOVERVIEW_GROUPING_FAVOURITES; } if ($config->displaygroupinghidden == true) { return BLOCK_MYOVERVIEW_GROUPING_HIDDEN; } if ($config->displaygroupingcustomfield == true) { return BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD; } // In this case, no grouping option is enabled and the grouping is not needed at all. // But it's better not to leave $this->grouping unset for any unexpected case. return BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN; } /** * Set the available layouts based on the config table settings, * if none are available, defaults to the cards view. * * @throws \dml_exception * */ public function set_available_layouts() { if ($config = get_config('block_myoverview', 'layouts')) { $this->layouts = explode(',', $config); } else { $this->layouts = array(BLOCK_MYOVERVIEW_VIEW_CARD); } } /** * Get the user preferences as an array to figure out what has been selected. * * @return array $preferences Array with the pref as key and value set to true */ public function get_preferences_as_booleans() { $preferences = []; $preferences[$this->sort] = true; $preferences[$this->grouping] = true; // Only use the user view/display preference if it is in available layouts. if (in_array($this->view, $this->layouts)) { $preferences[$this->view] = true; } else { $preferences[reset($this->layouts)] = true; } return $preferences; } /** * Format a layout into an object for export as a Context variable to template. * * @param string $layoutname * * @return \stdClass $layout an object representation of a layout * @throws \coding_exception */ public function format_layout_for_export($layoutname) { $layout = new stdClass(); $layout->id = $layoutname; $layout->name = get_string($layoutname, 'block_myoverview'); $layout->active = $this->view == $layoutname ? true : false; $layout->arialabel = get_string('aria:' . $layoutname, 'block_myoverview'); return $layout; } /** * Get the available layouts formatted for export. * * @return array an array of objects representing available layouts */ public function get_formatted_available_layouts_for_export() { return array_map(array($this, 'format_layout_for_export'), $this->layouts); } /** * Get the list of values to add to the grouping dropdown * @return object[] containing name, value and active fields */ public function get_customfield_values_for_export() { global $DB, $USER; if (!$this->displaygroupingcustomfield) { return []; } // Get the relevant customfield ID within the core_course/course component/area. $fieldid = $DB->get_field_sql(" SELECT f.id FROM {customfield_field} f JOIN {customfield_category} c ON c.id = f.categoryid WHERE f.shortname = :shortname AND c.component = 'core_course' AND c.area = 'course' ", ['shortname' => $this->customfiltergrouping]); if (!$fieldid) { return []; } $courses = enrol_get_all_users_courses($USER->id, true); if (!$courses) { return []; } list($csql, $params) = $DB->get_in_or_equal(array_keys($courses), SQL_PARAMS_NAMED); $select = "instanceid $csql AND fieldid = :fieldid"; $params['fieldid'] = $fieldid; $distinctablevalue = $DB->sql_compare_text('value'); $values = $DB->get_records_select_menu('customfield_data', $select, $params, '', "DISTINCT $distinctablevalue, $distinctablevalue AS value2"); \core_collator::asort($values, \core_collator::SORT_NATURAL); $values = array_filter($values); if (!$values) { return []; } $field = \core_customfield\field_controller::create($fieldid); $isvisible = $field->get_configdata_property('visibility') == \core_course\customfield\course_handler::VISIBLETOALL; // Only visible fields to everybody supporting course grouping will be displayed. if (!$field->supports_course_grouping() || !$isvisible) { return []; // The field shouldn't have been selectable in the global settings, but just skip it now. } $values = $field->course_grouping_format_values($values); $customfieldactive = ($this->grouping === BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD); $ret = []; foreach ($values as $value => $name) { $ret[] = (object)[ 'name' => $name, 'value' => $value, 'active' => ($customfieldactive && ($this->customfieldvalue == $value)), ]; } return $ret; } /** * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output * @return array Context variables for the template * @throws \coding_exception * */ public function export_for_template(renderer_base $output) { global $CFG, $USER; $nocoursesurl = $output->image_url('courses', 'block_myoverview')->out(); $newcourseurl = ''; $coursecat = \core_course_category::user_top(); if ($coursecat && ($category = \core_course_category::get_nearest_editable_subcategory($coursecat, ['create']))) { $newcourseurl = new \moodle_url('/course/edit.php', ['category' => $category->id]); } $customfieldvalues = $this->get_customfield_values_for_export(); $selectedcustomfield = ''; if ($this->grouping == BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD) { foreach ($customfieldvalues as $field) { if ($field->value == $this->customfieldvalue) { $selectedcustomfield = $field->name; break; } } // If the selected custom field value has not been found (possibly because the field has // been changed in the settings) find a suitable fallback. if (!$selectedcustomfield) { $this->grouping = $this->get_fallback_grouping(get_config('block_myoverview')); if ($this->grouping == BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD) { // If the fallback grouping is still customfield, then select the first field. $firstfield = reset($customfieldvalues); if ($firstfield) { $selectedcustomfield = $firstfield->name; $this->customfieldvalue = $firstfield->value; } } } } $preferences = $this->get_preferences_as_booleans(); $availablelayouts = $this->get_formatted_available_layouts_for_export(); $sort = ''; if ($this->sort == BLOCK_MYOVERVIEW_SORTING_SHORTNAME) { $sort = 'shortname'; } else { $sort = $this->sort == BLOCK_MYOVERVIEW_SORTING_TITLE ? 'fullname' : 'ul.timeaccess desc'; } $defaultvariables = [ 'totalcoursecount' => count(enrol_get_all_users_courses($USER->id, true)), 'nocoursesimg' => $nocoursesurl, 'newcourseurl' => $newcourseurl, 'grouping' => $this->grouping, 'sort' => $sort, // If the user preference display option is not available, default to first available layout. 'view' => in_array($this->view, $this->layouts) ? $this->view : reset($this->layouts), 'paging' => $this->paging, 'layouts' => $availablelayouts, 'displaycategories' => $this->displaycategories, 'displaydropdown' => (count($availablelayouts) > 1) ? true : false, 'displaygroupingallincludinghidden' => $this->displaygroupingallincludinghidden, 'displaygroupingall' => $this->displaygroupingall, 'displaygroupinginprogress' => $this->displaygroupinginprogress, 'displaygroupingfuture' => $this->displaygroupingfuture, 'displaygroupingpast' => $this->displaygroupingpast, 'displaygroupingfavourites' => $this->displaygroupingfavourites, 'displaygroupinghidden' => $this->displaygroupinghidden, 'displaygroupingselector' => $this->displaygroupingselector, 'displaygroupingcustomfield' => $this->displaygroupingcustomfield && $customfieldvalues, 'customfieldname' => $this->customfiltergrouping, 'customfieldvalue' => $this->customfieldvalue, 'customfieldvalues' => $customfieldvalues, 'selectedcustomfield' => $selectedcustomfield, 'showsortbyshortname' => $CFG->courselistshortnames, ]; return array_merge($defaultvariables, $preferences); } /** * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output * @return array Context variables for the template * @throws \coding_exception * */ public function export_for_zero_state_template(renderer_base $output) { global $CFG, $DB; $nocoursesimg = $output->image_url('courses', 'block_myoverview'); $buttons = []; $coursecat = \core_course_category::user_top(); if ($coursecat) { // Request a course button. $category = \core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/course:request']); if ($category && $category->can_request_course()) { $requestbutton = new \single_button( new \moodle_url('/course/request.php', ['category' => $category->id]), get_string('requestcourse'), 'post', \single_button::BUTTON_PRIMARY ); $buttons[] = $requestbutton->export_for_template($output); return $this->generate_zero_state_data( $nocoursesimg, $buttons, [ 'title' => 'zero_request_title', 'intro' => ($CFG->coursecreationguide ? 'zero_request_intro' : 'zero_nocourses_intro'), ], ); } $totalcourses = $DB->count_records_select('course', 'category > 0'); if ($coursecat) { // Manage courses or categories button. $managebuttonname = get_string('managecategories'); if ($totalcourses) { $managebuttonname = get_string('managecourses'); } if ($categorytomanage = \core_course_category::get_nearest_editable_subcategory($coursecat, ['manage'])) { $managebutton = new \single_button( new \moodle_url('/course/management.php', ['category' => $categorytomanage->id]), $managebuttonname, ); $buttons[] = $managebutton->export_for_template($output); } } // Create course button. if ($category = \core_course_category::get_nearest_editable_subcategory($coursecat, ['create'])) { $createbutton = new \single_button( new \moodle_url('/course/edit.php', ['category' => $category->id]), get_string('createcourse', 'block_myoverview'), 'post', \single_button::BUTTON_PRIMARY, ); $buttons[] = $createbutton->export_for_template($output); $title = $totalcourses ? 'zero_default_title' : 'zero_nocourses_title'; $intro = $totalcourses ? 'zero_default_intro' : ($CFG->coursecreationguide ? 'zero_request_intro' : 'zero_nocourses_intro'); return $this->generate_zero_state_data( $nocoursesimg, $buttons, ['title' => $title, 'intro' => $intro], ); } } return $this->generate_zero_state_data( $nocoursesimg, $buttons, ['title' => 'zero_default_title', 'intro' => 'zero_default_intro'] ); } /** * Generate the state zero data. * * @param \moodle_url $imageurl The URL to the image to show * @param string[] $buttons Exported {@see \single_button} instances * @param array $strings Title and intro strings for the zero state if needed. * @return array Context variables for the template */ private function generate_zero_state_data(\moodle_url $imageurl, array $buttons, array $strings) { global $CFG; // Documentation data. $dochref = new \moodle_url($CFG->docroot, ['lang' => current_language()]); $docparams = [ 'dochref' => $dochref->out(), 'doctitle' => get_string('documentation'), 'doctarget' => $CFG->doctonewwindow ? '_blank' : '_self', ]; if ($CFG->coursecreationguide) { // Add quickstart guide link. $quickstart = new \moodle_url($CFG->coursecreationguide, ['lang' => current_language()]); $docparams += [ 'quickhref' => $quickstart->out(), 'quicktitle' => get_string('viewquickstart', 'block_myoverview'), 'quicktarget' => '_blank', ]; } return [ 'nocoursesimg' => $imageurl->out(), 'title' => ($strings['title']) ? get_string($strings['title'], 'block_myoverview') : '', 'intro' => ($strings['intro']) ? get_string($strings['intro'], 'block_myoverview', $docparams) : '', 'buttons' => $buttons, ]; } } PK'9\^dZy!!&myoverview/classes/output/renderer.phpnu[. /** * myoverview block rendrer * * @package block_myoverview * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_myoverview\output; defined('MOODLE_INTERNAL') || die; use plugin_renderer_base; use renderable; /** * myoverview block renderer * * @package block_myoverview * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends plugin_renderer_base { /** * Return the main content for the block overview. * * @param main $main The main renderable * @return string HTML string */ public function render_main(main $main) { global $USER; if (!count(enrol_get_all_users_courses($USER->id, true))) { return $this->render_from_template( 'block_myoverview/zero-state', $main->export_for_zero_state_template($this) ); } return $this->render_from_template('block_myoverview/main', $main->export_for_template($this)); } } PK'9\y 2myoverview/templates/nav-display-selector.mustachenu[{{! 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 . }} {{! @template block_myoverview/nav-display-selector This template renders display dropdown. Example context (json): { "cards": true, "list": false, "summary": false } }} PK'9\-"myoverview/templates/main.mustachenu[{{! 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 . }} {{! @template block_myoverview/main This template renders the main content area for the myoverview block. Example context (json): {} }}
              {{> block_myoverview/courses-view }}
              {{#js}} require( [ 'jquery', 'block_myoverview/main', ], function( $, Main ) { var root = $('#block-myoverview-{{uniqid}}'); Main.init(root); }); {{/js}} PK'9\C4 4 /myoverview/templates/nav-sort-selector.mustachenu[{{! 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 . }} {{! @template block_myoverview/nav-sort-selector This template renders sorting dropdown. Example context (json): { "title": false, "lastaccessed": true } }}
              PK'9\zG~FF:myoverview/templates/placeholder-course-list-item.mustachenu[{{! 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 . }} {{! @template block_myoverview/placeholder-course-list-item This template renders the loading placeholder for the list & summary course items. Example context (json): {} }}
            • PK'9\b/myoverview/templates/nav-search-widget.mustachenu[{{! 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 . }} {{! @template block_myoverview/nav-search-widget This template renders the search input within the filters area. Example context (json): {} }}
              {{< core/search_input_auto }} {{$label}}{{#str}} searchcourses, block_myoverview {{/str}}{{/label}} {{$placeholder}}{{#str}} search, core {{/str}}{{/placeholder}} {{/ core/search_input_auto }}
              PK'9\k2//*myoverview/templates/progress-bar.mustachenu[{{! 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 . }} {{! @template block_myoverview/progress-bar This template renders a simple progress bar. Example context (json): { "progress": 50 } }}
              {{#str}}aria:courseprogress, block_myoverview{{/str}} {{#str}}completepercent, block_myoverview, {{progress}}{{/str}}
              PK'9\{3myoverview/templates/nav-grouping-selector.mustachenu[{{! 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 . }} {{! @template block_myoverview/nav-grouping-selector This template renders grouping dropdown. Example context (json): { "allincludinghidden": false, "all": true, "inprogress": false, "future": false, "past": false, "favourites": false, "hidden": false, "displaygroupingallincludinghidden": false, "displaygroupingall": true, "displaygroupinginprogress": true, "displaygroupingfuture": true, "displaygroupingpast": true, "displaygroupingfavourites": true, "displaygroupinghidden": true, "displaygroupingselector": true } }} {{#displaygroupingselector}} {{/displaygroupingselector}} {{^displaygroupingselector}}
               
              {{/displaygroupingselector}} PK'9\j'myoverview/templates/view-list.mustachenu[{{! 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 . }} {{! @template block_myoverview/view-list This template renders the list view for the myoverview block. Example context (json): { "courses": [ { "name": "Assignment due 1", "viewurl": "https://moodlesite/course/view.php?id=2", "courseimage": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg", "fullname": "course 3", "hasprogress": true, "progress": 10, "coursecategory": "Category 1", "visible": true } ] } }} PK'9\ᮤ*myoverview/templates/courses-view.mustachenu[{{! 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 . }} {{! @template block_myoverview/courses-view This template renders the courses view for the myoverview block. Example context (json): { "nocoursesimg": "https://moodlesite/theme/image.php/boost/block_myoverview/1535727318/courses", "newcourseurl": "https://moodlesite/course/edit.php", "grouping": "all", "sort": "fullname", "view": "card" } }}
              {{> block_myoverview/placeholders }}
              PK'9\,V (myoverview/templates/view-cards.mustachenu[{{! 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 . }} {{! @template block_myoverview/view-cards This template renders the cards view for the myoverview block. Example context (json): { "courses": [ { "name": "Assignment due 1", "viewurl": "https://moodlesite/course/view.php?id=2", "courseimage": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg", "fullname": "Course 3 for \"Statistical and Computational Tools\" ", "hasprogress": true, "progress": 10, "coursecategory": "Category 1", "visible": true } ] } }} {{< core_course/coursecards }} {{$menu}} {{/menu}} {{$progress}} {{#hasprogress}} {{/hasprogress}} {{/progress}} {{$coursename}} {{{fullname}}} {{/coursename}} {{$coursecategory}} {{#showcoursecategory}} {{#str}}aria:coursecategory, core_course{{/str}} {{{coursecategory}}} {{/showcoursecategory}} {{/coursecategory}} {{/ core_course/coursecards }} PK'9\AK99(myoverview/templates/zero-state.mustachenu[{{! 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 . }} {{! @template block_myoverview/zero-state This template renders the main content area when there is no course to show. Example context (json): { "nocoursesimg": "https://moodle.org/img/nocourses.svg", "title": "No courses", "text": "Moodle community", "intro": "Come back later", "buttons": [ { "id": "buttons1", "method": "get", "url": "#", "primary": true, "label": "Button1" }, { "id": "buttons2", "method": "get", "url": "#", "primary": false, "label": "Button2" } ] } }}

              {{{ title }}}

              {{{ intro }}}

              {{#buttons}} {{>core/single_button}} {{/buttons}}
              PK'9\ಜ*myoverview/templates/view-summary.mustachenu[{{! 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 . }} {{! @template block_myoverview/view-summary This template renders the list view for the myoverview block. Example context (json): { "courses": [ { "name": "Assignment due 1", "viewurl": "https://moodlesite/course/view.php?id=2", "courseimage": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg", "fullname": "course 3", "summary": "This course is about assignments", "hasprogress": true, "progress": 10, "coursecategory": "Category 1", "visible": true } ] } }}
              {{#courses}}
              {{#showshortname}}
              {{#str}}aria:courseshortname, core_course{{/str}}
              {{{shortname}}}
              {{/showshortname}} {{> core_course/favouriteicon }} {{#str}}aria:coursename, core_course{{/str}} {{{fullname}}} {{$coursecategory}} {{#str}}aria:coursecategory, core_course{{/str}} {{#showcoursecategory}} {{#coursecategory}} {{{coursecategory}}} {{/coursecategory}} {{/showcoursecategory}} {{/coursecategory}} {{^visible}}
              {{#str}} hiddenfromstudents {{/str}}
              {{/visible}}
              {{#str}}aria:coursesummary, block_myoverview{{/str}} {{{summary}}}
              {{#hasprogress}}
              {{> block_myoverview/progress-bar}}
              {{/hasprogress}}
              {{/courses}}
              PK'9\X 0myoverview/templates/course-action-menu.mustachenu[{{! 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 . }} {{! @template block_myoverview/course-action-menu This template renders action menu for each course. Example context (json): { "isfavourite": true } }} PK'9\*myoverview/templates/placeholders.mustachenu[{{! 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 . }} {{! @template block_myoverview/placeholders This template renders the loading placeholders for the myoverview block. Example context (json): {} }} PK'9\zSjmyoverview/upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This file describes API changes in the myoverview block code. === 3.7 === * The 'block/myoverview:addinstance' capability has been removed. It has never been used in code. === 3.9 === * Rename setting block_myoverview->displaygroupingstarred to block_myoverview->displaygroupingfavourites. PK'9\|Jehyhymyoverview/amd/src/view.jsnu[// 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 . /** * Manage the courses view for the overview block. * * @copyright 2018 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import $ from 'jquery'; import * as Repository from 'block_myoverview/repository'; import * as PagedContentFactory from 'core/paged_content_factory'; import * as PubSub from 'core/pubsub'; import * as CustomEvents from 'core/custom_interaction_events'; import * as Notification from 'core/notification'; import * as Templates from 'core/templates'; import * as CourseEvents from 'core_course/events'; import SELECTORS from 'block_myoverview/selectors'; import * as PagedContentEvents from 'core/paged_content_events'; import * as Aria from 'core/aria'; import {debounce} from 'core/utils'; import {setUserPreference} from 'core_user/repository'; const TEMPLATES = { COURSES_CARDS: 'block_myoverview/view-cards', COURSES_LIST: 'block_myoverview/view-list', COURSES_SUMMARY: 'block_myoverview/view-summary', NOCOURSES: 'core_course/no-courses' }; const GROUPINGS = { GROUPING_ALLINCLUDINGHIDDEN: 'allincludinghidden', GROUPING_ALL: 'all', GROUPING_INPROGRESS: 'inprogress', GROUPING_FUTURE: 'future', GROUPING_PAST: 'past', GROUPING_FAVOURITES: 'favourites', GROUPING_HIDDEN: 'hidden' }; const NUMCOURSES_PERPAGE = [12, 24, 48, 96, 0]; let loadedPages = []; let courseOffset = 0; let lastPage = 0; let lastLimit = 0; let namespace = null; /** * Whether the summary display has been loaded. * * If true, this means that courses have been loaded with the summary text. * Otherwise, switching to the summary display mode will require course data to be fetched with the summary text. * * @type {boolean} */ let summaryDisplayLoaded = false; /** * Get filter values from DOM. * * @param {object} root The root element for the courses view. * @return {filters} Set filters. */ const getFilterValues = root => { const courseRegion = root.find(SELECTORS.courseView.region); return { display: courseRegion.attr('data-display'), grouping: courseRegion.attr('data-grouping'), sort: courseRegion.attr('data-sort'), displaycategories: courseRegion.attr('data-displaycategories'), customfieldname: courseRegion.attr('data-customfieldname'), customfieldvalue: courseRegion.attr('data-customfieldvalue'), }; }; // We want the paged content controls below the paged content area. // and the controls should be ignored while data is loading. const DEFAULT_PAGED_CONTENT_CONFIG = { ignoreControlWhileLoading: true, controlPlacementBottom: true, persistentLimitKey: 'block_myoverview_user_paging_preference' }; /** * Get enrolled courses from backend. * * @param {object} filters The filters for this view. * @param {int} limit The number of courses to show. * @return {promise} Resolved with an array of courses. */ const getMyCourses = (filters, limit) => { const params = { offset: courseOffset, limit: limit, classification: filters.grouping, sort: filters.sort, customfieldname: filters.customfieldname, customfieldvalue: filters.customfieldvalue, }; if (filters.display === 'summary') { params.requiredfields = Repository.SUMMARY_REQUIRED_FIELDS; summaryDisplayLoaded = true; } else { params.requiredfields = Repository.CARDLIST_REQUIRED_FIELDS; } return Repository.getEnrolledCoursesByTimeline(params); }; /** * Search for enrolled courses from backend. * * @param {object} filters The filters for this view. * @param {int} limit The number of courses to show. * @param {string} searchValue What does the user want to search within their courses. * @return {promise} Resolved with an array of courses. */ const getSearchMyCourses = (filters, limit, searchValue) => { const params = { offset: courseOffset, limit: limit, classification: 'search', sort: filters.sort, customfieldname: filters.customfieldname, customfieldvalue: filters.customfieldvalue, searchvalue: searchValue, }; if (filters.display === 'summary') { params.requiredfields = Repository.SUMMARY_REQUIRED_FIELDS; summaryDisplayLoaded = true; } else { params.requiredfields = Repository.CARDLIST_REQUIRED_FIELDS; summaryDisplayLoaded = false; } return Repository.getEnrolledCoursesByTimeline(params); }; /** * Get the container element for the favourite icon. * * @param {Object} root The course overview container * @param {Number} courseId Course id number * @return {Object} The favourite icon container */ const getFavouriteIconContainer = (root, courseId) => { return root.find(SELECTORS.FAVOURITE_ICON + '[data-course-id="' + courseId + '"]'); }; /** * Get the paged content container element. * * @param {Object} root The course overview container * @param {Number} index Rendered page index. * @return {Object} The rendered paged container. */ const getPagedContentContainer = (root, index) => { return root.find('[data-region="paged-content-page"][data-page="' + index + '"]'); }; /** * Get the course id from a favourite element. * * @param {Object} root The favourite icon container element. * @return {Number} Course id. */ const getCourseId = root => { return root.attr('data-course-id'); }; /** * Hide the favourite icon. * * @param {Object} root The favourite icon container element. * @param {Number} courseId Course id number. */ const hideFavouriteIcon = (root, courseId) => { const iconContainer = getFavouriteIconContainer(root, courseId); const isFavouriteIcon = iconContainer.find(SELECTORS.ICON_IS_FAVOURITE); isFavouriteIcon.addClass('hidden'); Aria.hide(isFavouriteIcon); const notFavourteIcon = iconContainer.find(SELECTORS.ICON_NOT_FAVOURITE); notFavourteIcon.removeClass('hidden'); Aria.unhide(notFavourteIcon); }; /** * Show the favourite icon. * * @param {Object} root The course overview container. * @param {Number} courseId Course id number. */ const showFavouriteIcon = (root, courseId) => { const iconContainer = getFavouriteIconContainer(root, courseId); const isFavouriteIcon = iconContainer.find(SELECTORS.ICON_IS_FAVOURITE); isFavouriteIcon.removeClass('hidden'); Aria.unhide(isFavouriteIcon); const notFavourteIcon = iconContainer.find(SELECTORS.ICON_NOT_FAVOURITE); notFavourteIcon.addClass('hidden'); Aria.hide(notFavourteIcon); }; /** * Get the action menu item * * @param {Object} root The course overview container * @param {Number} courseId Course id. * @return {Object} The add to favourite menu item. */ const getAddFavouriteMenuItem = (root, courseId) => { return root.find('[data-action="add-favourite"][data-course-id="' + courseId + '"]'); }; /** * Get the action menu item * * @param {Object} root The course overview container * @param {Number} courseId Course id. * @return {Object} The remove from favourites menu item. */ const getRemoveFavouriteMenuItem = (root, courseId) => { return root.find('[data-action="remove-favourite"][data-course-id="' + courseId + '"]'); }; /** * Add course to favourites * * @param {Object} root The course overview container * @param {Number} courseId Course id number */ const addToFavourites = (root, courseId) => { const removeAction = getRemoveFavouriteMenuItem(root, courseId); const addAction = getAddFavouriteMenuItem(root, courseId); setCourseFavouriteState(courseId, true).then(success => { if (success) { PubSub.publish(CourseEvents.favourited, courseId); removeAction.removeClass('hidden'); addAction.addClass('hidden'); showFavouriteIcon(root, courseId); } else { Notification.alert('Starring course failed', 'Could not change favourite state'); } return; }).catch(Notification.exception); }; /** * Remove course from favourites * * @param {Object} root The course overview container * @param {Number} courseId Course id number */ const removeFromFavourites = (root, courseId) => { const removeAction = getRemoveFavouriteMenuItem(root, courseId); const addAction = getAddFavouriteMenuItem(root, courseId); setCourseFavouriteState(courseId, false).then(success => { if (success) { PubSub.publish(CourseEvents.unfavorited, courseId); removeAction.addClass('hidden'); addAction.removeClass('hidden'); hideFavouriteIcon(root, courseId); } else { Notification.alert('Starring course failed', 'Could not change favourite state'); } return; }).catch(Notification.exception); }; /** * Get the action menu item * * @param {Object} root The course overview container * @param {Number} courseId Course id. * @return {Object} The hide course menu item. */ const getHideCourseMenuItem = (root, courseId) => { return root.find('[data-action="hide-course"][data-course-id="' + courseId + '"]'); }; /** * Get the action menu item * * @param {Object} root The course overview container * @param {Number} courseId Course id. * @return {Object} The show course menu item. */ const getShowCourseMenuItem = (root, courseId) => { return root.find('[data-action="show-course"][data-course-id="' + courseId + '"]'); }; /** * Hide course * * @param {Object} root The course overview container * @param {Number} courseId Course id number */ const hideCourse = (root, courseId) => { const hideAction = getHideCourseMenuItem(root, courseId); const showAction = getShowCourseMenuItem(root, courseId); const filters = getFilterValues(root); setCourseHiddenState(courseId, true); // Remove the course from this view as it is now hidden and thus not covered by this view anymore. // Do only if we are not in "All (including archived)" view mode where really all courses are shown. if (filters.grouping !== GROUPINGS.GROUPING_ALLINCLUDINGHIDDEN) { hideElement(root, courseId); } hideAction.addClass('hidden'); showAction.removeClass('hidden'); }; /** * Show course * * @param {Object} root The course overview container * @param {Number} courseId Course id number */ const showCourse = (root, courseId) => { const hideAction = getHideCourseMenuItem(root, courseId); const showAction = getShowCourseMenuItem(root, courseId); const filters = getFilterValues(root); setCourseHiddenState(courseId, null); // Remove the course from this view as it is now shown again and thus not covered by this view anymore. // Do only if we are not in "All (including archived)" view mode where really all courses are shown. if (filters.grouping !== GROUPINGS.GROUPING_ALLINCLUDINGHIDDEN) { hideElement(root, courseId); } hideAction.removeClass('hidden'); showAction.addClass('hidden'); }; /** * Set the courses hidden status and push to repository * * @param {Number} courseId Course id to favourite. * @param {Boolean} status new hidden status. * @return {Promise} Repository promise. */ const setCourseHiddenState = (courseId, status) => { // If the given status is not hidden, the preference has to be deleted with a null value. if (status === false) { status = null; } return setUserPreference(`block_myoverview_hidden_course_${courseId}`, status) .catch(Notification.exception); }; /** * Reset the loadedPages dataset to take into account the hidden element * * @param {Object} root The course overview container * @param {Number} id The course id number */ const hideElement = (root, id) => { const pagingBar = root.find('[data-region="paging-bar"]'); const jumpto = parseInt(pagingBar.attr('data-active-page-number')); // Get a reduced dataset for the current page. const courseList = loadedPages[jumpto]; let reducedCourse = courseList.courses.reduce((accumulator, current) => { if (+id !== +current.id) { accumulator.push(current); } return accumulator; }, []); // Get the next page's data if loaded and pop the first element from it. if (typeof (loadedPages[jumpto + 1]) !== 'undefined') { const newElement = loadedPages[jumpto + 1].courses.slice(0, 1); // Adjust the dataset for the reset of the pages that are loaded. loadedPages.forEach((courseList, index) => { if (index > jumpto) { let popElement = []; if (typeof (loadedPages[index + 1]) !== 'undefined') { popElement = loadedPages[index + 1].courses.slice(0, 1); } loadedPages[index].courses = [...loadedPages[index].courses.slice(1), ...popElement]; } }); reducedCourse = [...reducedCourse, ...newElement]; } // Check if the next page is the last page and if it still has data associated to it. if (lastPage === jumpto + 1 && loadedPages[jumpto + 1].courses.length === 0) { const pagedContentContainer = root.find('[data-region="paged-content-container"]'); PagedContentFactory.resetLastPageNumber($(pagedContentContainer).attr('id'), jumpto); } loadedPages[jumpto].courses = reducedCourse; // Reduce the course offset. courseOffset--; // Render the paged content for the current. const pagedContentPage = getPagedContentContainer(root, jumpto); renderCourses(root, loadedPages[jumpto]).then((html, js) => { return Templates.replaceNodeContents(pagedContentPage, html, js); }).catch(Notification.exception); // Delete subsequent pages in order to trigger the callback. loadedPages.forEach((courseList, index) => { if (index > jumpto) { const page = getPagedContentContainer(root, index); page.remove(); } }); }; /** * Set the courses favourite status and push to repository * * @param {Number} courseId Course id to favourite. * @param {boolean} status new favourite status. * @return {Promise} Repository promise. */ const setCourseFavouriteState = (courseId, status) => { return Repository.setFavouriteCourses({ courses: [ { 'id': courseId, 'favourite': status } ] }).then(result => { if (result.warnings.length === 0) { loadedPages.forEach(courseList => { courseList.courses.forEach((course, index) => { if (course.id == courseId) { courseList.courses[index].isfavourite = status; } }); }); return true; } else { return false; } }).catch(Notification.exception); }; /** * Given there are no courses to render provide the rendered template. * * @param {object} root The root element for the courses view. * @return {promise} jQuery promise resolved after rendering is complete. */ const noCoursesRender = root => { const nocoursesimg = root.find(SELECTORS.courseView.region).attr('data-nocoursesimg'); const newcourseurl = root.find(SELECTORS.courseView.region).attr('data-newcourseurl'); return Templates.render(TEMPLATES.NOCOURSES, { nocoursesimg: nocoursesimg, newcourseurl: newcourseurl }); }; /** * Render the dashboard courses. * * @param {object} root The root element for the courses view. * @param {array} coursesData containing array of returned courses. * @return {promise} jQuery promise resolved after rendering is complete. */ const renderCourses = (root, coursesData) => { const filters = getFilterValues(root); let currentTemplate = ''; if (filters.display === 'card') { currentTemplate = TEMPLATES.COURSES_CARDS; } else if (filters.display === 'list') { currentTemplate = TEMPLATES.COURSES_LIST; } else { currentTemplate = TEMPLATES.COURSES_SUMMARY; } if (!coursesData) { return noCoursesRender(root); } else { // Sometimes we get weird objects coming after a failed search, cast to ensure typing functions. if (Array.isArray(coursesData.courses) === false) { coursesData.courses = Object.values(coursesData.courses); } // Whether the course category should be displayed in the course item. coursesData.courses = coursesData.courses.map(course => { course.showcoursecategory = filters.displaycategories === 'on'; return course; }); if (coursesData.courses.length) { return Templates.render(currentTemplate, { courses: coursesData.courses, }); } else { return noCoursesRender(root); } } }; /** * Return the callback to be passed to the subscribe event * * @param {object} root The root element for the courses view * @return {function} Partially applied function that'll execute when passed a limit */ const setLimit = root => { // @param {Number} limit The paged limit that is passed through the event. return limit => root.find(SELECTORS.courseView.region).attr('data-paging', limit); }; /** * Intialise the paged list and cards views on page load. * Returns an array of paged contents that we would like to handle here * * @param {object} root The root element for the courses view * @param {string} namespace The namespace for all the events attached */ const registerPagedEventHandlers = (root, namespace) => { const event = namespace + PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT; PubSub.subscribe(event, setLimit(root)); }; /** * Figure out how many items are going to be allowed to be rendered in the block. * * @param {Number} pagingLimit How many courses to display * @param {Object} root The course overview container * @return {Number[]} How many courses will be rendered */ const itemsPerPageFunc = (pagingLimit, root) => { let itemsPerPage = NUMCOURSES_PERPAGE.map(value => { let active = false; if (value === pagingLimit) { active = true; } return { value: value, active: active }; }); // Filter out all pagination options which are too large for the amount of courses user is enrolled in. const totalCourseCount = parseInt(root.find(SELECTORS.courseView.region).attr('data-totalcoursecount'), 10); return itemsPerPage.filter(pagingOption => { if (pagingOption.value === 0 && totalCourseCount > 100) { // To minimise performance issues, do not show the "All" option if the user is enrolled in more than 100 courses. return false; } return pagingOption.value < totalCourseCount; }); }; /** * Mutates and controls the loadedPages array and handles the bootstrapping. * * @param {Array|Object} coursesData Array of all of the courses to start building the page from * @param {Number} currentPage What page are we currently on? * @param {Object} pageData Any current page information * @param {Object} actions Paged content helper * @param {null|boolean} activeSearch Are we currently actively searching and building up search results? */ const pageBuilder = (coursesData, currentPage, pageData, actions, activeSearch = null) => { // If the courseData comes in an object then get the value otherwise it is a pure array. let courses = coursesData.courses ? coursesData.courses : coursesData; let nextPageStart = 0; let pageCourses = []; // If current page's data is loaded make sure we max it to page limit. if (typeof (loadedPages[currentPage]) !== 'undefined') { pageCourses = loadedPages[currentPage].courses; const currentPageLength = pageCourses.length; if (currentPageLength < pageData.limit) { nextPageStart = pageData.limit - currentPageLength; pageCourses = {...loadedPages[currentPage].courses, ...courses.slice(0, nextPageStart)}; } } else { // When the page limit is zero, there is only one page of courses, no start for next page. nextPageStart = pageData.limit || false; pageCourses = (pageData.limit > 0) ? courses.slice(0, pageData.limit) : courses; } // Finished setting up the current page. loadedPages[currentPage] = { courses: pageCourses }; // Set up the next page (if there is more than one page). const remainingCourses = nextPageStart !== false ? courses.slice(nextPageStart, courses.length) : []; if (remainingCourses.length) { loadedPages[currentPage + 1] = { courses: remainingCourses }; } // Set the last page to either the current or next page. if (loadedPages[currentPage].courses.length < pageData.limit || !remainingCourses.length) { lastPage = currentPage; if (activeSearch === null) { actions.allItemsLoaded(currentPage); } } else if (typeof (loadedPages[currentPage + 1]) !== 'undefined' && loadedPages[currentPage + 1].courses.length < pageData.limit) { lastPage = currentPage + 1; } courseOffset = coursesData.nextoffset; }; /** * In cases when switching between regular rendering and search rendering we need to reset some variables. */ const resetGlobals = () => { courseOffset = 0; loadedPages = []; lastPage = 0; lastLimit = 0; }; /** * The default functionality of fetching paginated courses without special handling. * * @return {function(Object, Object, Object, Object, Object, Promise, Number): void} */ const standardFunctionalityCurry = () => { resetGlobals(); return (filters, currentPage, pageData, actions, root, promises, limit) => { const pagePromise = getMyCourses( filters, limit ).then(coursesData => { pageBuilder(coursesData, currentPage, pageData, actions); return renderCourses(root, loadedPages[currentPage]); }).catch(Notification.exception); promises.push(pagePromise); }; }; /** * Initialize the searching functionality so we can call it when required. * * @return {function(Object, Number, Object, Object, Object, Promise, Number, String): void} */ const searchFunctionalityCurry = () => { resetGlobals(); return (filters, currentPage, pageData, actions, root, promises, limit, inputValue) => { const searchingPromise = getSearchMyCourses( filters, limit, inputValue ).then(coursesData => { pageBuilder(coursesData, currentPage, pageData, actions); return renderCourses(root, loadedPages[currentPage]); }).catch(Notification.exception); promises.push(searchingPromise); }; }; /** * Initialise the courses list and cards views on page load. * * @param {object} root The root element for the courses view. * @param {function} promiseFunction How do we fetch the courses and what do we do with them? * @param {null | string} inputValue What to search for */ const initializePagedContent = (root, promiseFunction, inputValue = null) => { const pagingLimit = parseInt(root.find(SELECTORS.courseView.region).attr('data-paging'), 10); let itemsPerPage = itemsPerPageFunc(pagingLimit, root); const config = {...{}, ...DEFAULT_PAGED_CONTENT_CONFIG}; config.eventNamespace = namespace; const pagedContentPromise = PagedContentFactory.createWithLimit( itemsPerPage, (pagesData, actions) => { let promises = []; pagesData.forEach(pageData => { const currentPage = pageData.pageNumber; let limit = (pageData.limit > 0) ? pageData.limit : 0; // Reset local variables if limits have changed. if (+lastLimit !== +limit) { loadedPages = []; courseOffset = 0; lastPage = 0; } if (lastPage === currentPage) { // If we are on the last page and have it's data then load it from cache. actions.allItemsLoaded(lastPage); promises.push(renderCourses(root, loadedPages[currentPage])); return; } lastLimit = limit; // Get 2 pages worth of data as we will need it for the hidden functionality. if (typeof (loadedPages[currentPage + 1]) === 'undefined') { if (typeof (loadedPages[currentPage]) === 'undefined') { limit *= 2; } } // Get the current applied filters. const filters = getFilterValues(root); // Call the curried function that'll handle the course promise and any manipulation of it. promiseFunction(filters, currentPage, pageData, actions, root, promises, limit, inputValue); }); return promises; }, config ); pagedContentPromise.then((html, js) => { registerPagedEventHandlers(root, namespace); return Templates.replaceNodeContents(root.find(SELECTORS.courseView.region), html, js); }).catch(Notification.exception); }; /** * Listen to, and handle events for the myoverview block. * * @param {Object} root The myoverview block container element. * @param {HTMLElement} page The whole HTMLElement for our block. */ const registerEventListeners = (root, page) => { CustomEvents.define(root, [ CustomEvents.events.activate ]); root.on(CustomEvents.events.activate, SELECTORS.ACTION_ADD_FAVOURITE, (e, data) => { const favourite = $(e.target).closest(SELECTORS.ACTION_ADD_FAVOURITE); const courseId = getCourseId(favourite); addToFavourites(root, courseId); data.originalEvent.preventDefault(); }); root.on(CustomEvents.events.activate, SELECTORS.ACTION_REMOVE_FAVOURITE, (e, data) => { const favourite = $(e.target).closest(SELECTORS.ACTION_REMOVE_FAVOURITE); const courseId = getCourseId(favourite); removeFromFavourites(root, courseId); data.originalEvent.preventDefault(); }); root.on(CustomEvents.events.activate, SELECTORS.FAVOURITE_ICON, (e, data) => { data.originalEvent.preventDefault(); }); root.on(CustomEvents.events.activate, SELECTORS.ACTION_HIDE_COURSE, (e, data) => { const target = $(e.target).closest(SELECTORS.ACTION_HIDE_COURSE); const courseId = getCourseId(target); hideCourse(root, courseId); data.originalEvent.preventDefault(); }); root.on(CustomEvents.events.activate, SELECTORS.ACTION_SHOW_COURSE, (e, data) => { const target = $(e.target).closest(SELECTORS.ACTION_SHOW_COURSE); const courseId = getCourseId(target); showCourse(root, courseId); data.originalEvent.preventDefault(); }); // Searching functionality event handlers. const input = page.querySelector(SELECTORS.region.searchInput); const clearIcon = page.querySelector(SELECTORS.region.clearIcon); clearIcon.addEventListener('click', () => { input.value = ''; input.focus(); clearSearch(clearIcon, root); }); input.addEventListener('input', debounce(() => { if (input.value === '') { clearSearch(clearIcon, root); } else { activeSearch(clearIcon); initializePagedContent(root, searchFunctionalityCurry(), input.value.trim()); } }, 1000)); }; /** * Reset the search icon and trigger the init for the block. * * @param {HTMLElement} clearIcon Our closing icon to manipulate. * @param {Object} root The myoverview block container element. */ export const clearSearch = (clearIcon, root) => { clearIcon.classList.add('d-none'); init(root); }; /** * Change the searching icon to its' active state. * * @param {HTMLElement} clearIcon Our closing icon to manipulate. */ const activeSearch = (clearIcon) => { clearIcon.classList.remove('d-none'); }; /** * Intialise the courses list and cards views on page load. * * @param {object} root The root element for the courses view. */ export const init = root => { root = $(root); loadedPages = []; lastPage = 0; courseOffset = 0; if (!root.attr('data-init')) { const page = document.querySelector(SELECTORS.region.selectBlock); registerEventListeners(root, page); namespace = "block_myoverview_" + root.attr('id') + "_" + Math.random(); root.attr('data-init', true); } initializePagedContent(root, standardFunctionalityCurry()); }; /** * Reset the courses views to their original * state on first page load.courseOffset * * This is called when configuration has changed for the event lists * to cause them to reload their data. * * @param {Object} root The root element for the timeline view. */ export const reset = root => { if (loadedPages.length > 0) { const filters = getFilterValues(root); // If the display mode is changed to 'summary' but the summary display has not been loaded yet, // we need to re-fetch the courses to include the course summary text. if (filters.display === 'summary' && !summaryDisplayLoaded) { const page = document.querySelector(SELECTORS.region.selectBlock); const input = page.querySelector(SELECTORS.region.searchInput); if (input.value !== '') { initializePagedContent(root, searchFunctionalityCurry(), input.value.trim()); } else { initializePagedContent(root, standardFunctionalityCurry()); } } else { loadedPages.forEach((courseList, index) => { let pagedContentPage = getPagedContentContainer(root, index); renderCourses(root, courseList).then((html, js) => { return Templates.replaceNodeContents(pagedContentPage, html, js); }).catch(Notification.exception); }); } } else { init(root); } }; PK'9\^(myoverview/amd/src/view_nav.jsnu[// 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 . /** * Manage the timeline view navigation for the overview block. * * @copyright 2018 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import $ from 'jquery'; import * as CustomEvents from 'core/custom_interaction_events'; import Notification from 'core/notification'; import {setUserPreference} from 'core_user/repository'; import * as View from 'block_myoverview/view'; import SELECTORS from 'block_myoverview/selectors'; /** * Update the user preference for the block. * * @param {String} filter The type of filter: display/sort/grouping. * @param {String} value The current preferred value. * @return {Promise} */ const updatePreferences = (filter, value) => { let type = null; if (filter === 'display') { type = 'block_myoverview_user_view_preference'; } else if (filter === 'sort') { type = 'block_myoverview_user_sort_preference'; } else if (filter === 'customfieldvalue') { type = 'block_myoverview_user_grouping_customfieldvalue_preference'; } else { type = 'block_myoverview_user_grouping_preference'; } return setUserPreference(type, value) .catch(Notification.exception); }; /** * Event listener for the Display filter (cards, list). * * @param {object} root The root element for the overview block */ const registerSelector = root => { const Selector = root.find(SELECTORS.FILTERS); CustomEvents.define(Selector, [CustomEvents.events.activate]); Selector.on( CustomEvents.events.activate, SELECTORS.FILTER_OPTION, (e, data) => { const option = $(e.target); if (option.hasClass('active')) { // If it's already active then we don't need to do anything. return; } const filter = option.attr('data-filter'); const pref = option.attr('data-pref'); const customfieldvalue = option.attr('data-customfieldvalue'); root.find(SELECTORS.courseView.region).attr('data-' + filter, option.attr('data-value')); updatePreferences(filter, pref); if (customfieldvalue) { root.find(SELECTORS.courseView.region).attr('data-customfieldvalue', customfieldvalue); updatePreferences('customfieldvalue', customfieldvalue); } // Reset the views. // Check if the user is currently in a searching state, if so we'll reset it. const page = document.querySelector(SELECTORS.region.selectBlock); const input = page.querySelector(SELECTORS.region.searchInput); if (input.value !== '') { const clearIcon = page.querySelector(SELECTORS.region.clearIcon); input.value = ''; // Triggers the init so wont need to call it again. View.clearSearch(clearIcon, root); } else { View.init(root); } data.originalEvent.preventDefault(); } ); Selector.on( CustomEvents.events.activate, SELECTORS.DISPLAY_OPTION, (e, data) => { const option = $(e.target); if (option.hasClass('active')) { return; } const filter = option.attr('data-display-option'); const pref = option.attr('data-pref'); root.find(SELECTORS.courseView.region).attr('data-display', option.attr('data-value')); updatePreferences(filter, pref); View.reset(root); data.originalEvent.preventDefault(); } ); }; /** * Initialise the timeline view navigation by adding event listeners to * the navigation elements. * * @param {object} root The root element for the myoverview block */ export const init = root => { root = $(root); registerSelector(root); }; PK'9\d`zr r myoverview/amd/src/repository.jsnu[// 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 . /** * A javascript module to retrieve enrolled coruses from the server. * * @module block_myoverview/repository * @copyright 2018 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import Ajax from 'core/ajax'; /** * Retrieve a list of enrolled courses. * * Valid args are: * string classification future, inprogress, past * int limit number of records to retreive * int Offset offset for pagination * int sort sort by lastaccess or name * * @method getEnrolledCoursesByTimeline * @param {object} args The request arguments * @return {promise} Resolved with an array of courses */ export const getEnrolledCoursesByTimeline = args => { const request = { methodname: 'core_course_get_enrolled_courses_by_timeline_classification', args: args }; return Ajax.call([request])[0]; }; /** * Set the favourite state on a list of courses. * * Valid args are: * Array courses list of course id numbers. * * @param {Object} args Arguments send to the webservice. * @return {Promise} Resolve with warnings. */ export const setFavouriteCourses = args => { const request = { methodname: 'core_course_set_favourite_courses', args: args }; return Ajax.call([request])[0]; }; /** * These course fields are the only ones needed to be included in the results for the card and list views. * * @type {string[]} */ export const CARDLIST_REQUIRED_FIELDS = [ 'id', 'fullname', 'shortname', 'showcoursecategory', 'showshortname', 'visible', 'enddate', ]; /** * These course fields are the only ones needed to be included in the results for the card and list views. * * @type {string[]} */ export const SUMMARY_REQUIRED_FIELDS = [ 'id', 'fullname', 'shortname', 'showcoursecategory', 'showshortname', 'visible', 'enddate', 'summary', 'summaryformat', ]; PK'9\\myoverview/amd/src/selectors.jsnu[// 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 . /** * Javascript to initialise the selectors for the myoverview block. * * @copyright 2018 Peter Dias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default { courseView: { region: '[data-region="courses-view"]', regionContent: '[data-region="course-view-content"]' }, FILTERS: '[data-region="filter"]', FILTER_OPTION: '[data-filter]', DISPLAY_OPTION: '[data-display-option]', ACTION_HIDE_COURSE: '[data-action="hide-course"]', ACTION_SHOW_COURSE: '[data-action="show-course"]', ACTION_ADD_FAVOURITE: '[data-action="add-favourite"]', ACTION_REMOVE_FAVOURITE: '[data-action="remove-favourite"]', FAVOURITE_ICON: '[data-region="favourite-icon"]', ICON_IS_FAVOURITE: '[data-region="is-favourite"]', ICON_NOT_FAVOURITE: '[data-region="not-favourite"]', region: { selectBlock: '[data-region="myoverview"]', clearIcon: '[data-action="clearsearch"]', searchInput: '[data-action="search"]', }, }; PK'9\f*myoverview/amd/src/main.jsnu[// 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 . /** * Javascript to initialise the myoverview block. * * @copyright 2018 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import * as View from 'block_myoverview/view'; import * as ViewNav from 'block_myoverview/view_nav'; /** * Initialise all of the modules for the overview block. * * @param {object} root The root element for the overview block. */ export const init = (root) => { // Initialise the course navigation elements. ViewNav.init(root); // Initialise the courses view modules. View.init(root); }; PK'9\/&myoverview/amd/build/repository.min.jsnu[define("block_myoverview/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj; /** * A javascript module to retrieve enrolled coruses from the server. * * @module block_myoverview/repository * @copyright 2018 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setFavouriteCourses=_exports.getEnrolledCoursesByTimeline=_exports.SUMMARY_REQUIRED_FIELDS=_exports.CARDLIST_REQUIRED_FIELDS=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getEnrolledCoursesByTimeline=args=>{const request={methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:args};return _ajax.default.call([request])[0]};_exports.setFavouriteCourses=args=>{const request={methodname:"core_course_set_favourite_courses",args:args};return _ajax.default.call([request])[0]};_exports.CARDLIST_REQUIRED_FIELDS=["id","fullname","shortname","showcoursecategory","showshortname","visible","enddate"];_exports.SUMMARY_REQUIRED_FIELDS=["id","fullname","shortname","showcoursecategory","showshortname","visible","enddate","summary","summaryformat"]})); //# sourceMappingURL=repository.min.js.mapPK'9\7II(myoverview/amd/build/view_nav.min.js.mapnu[{"version":3,"file":"view_nav.min.js","sources":["../src/view_nav.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Manage the timeline view navigation for the overview block.\n *\n * @copyright 2018 Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as CustomEvents from 'core/custom_interaction_events';\nimport Notification from 'core/notification';\nimport {setUserPreference} from 'core_user/repository';\nimport * as View from 'block_myoverview/view';\nimport SELECTORS from 'block_myoverview/selectors';\n\n/**\n * Update the user preference for the block.\n *\n * @param {String} filter The type of filter: display/sort/grouping.\n * @param {String} value The current preferred value.\n * @return {Promise}\n */\nconst updatePreferences = (filter, value) => {\n let type = null;\n if (filter === 'display') {\n type = 'block_myoverview_user_view_preference';\n } else if (filter === 'sort') {\n type = 'block_myoverview_user_sort_preference';\n } else if (filter === 'customfieldvalue') {\n type = 'block_myoverview_user_grouping_customfieldvalue_preference';\n } else {\n type = 'block_myoverview_user_grouping_preference';\n }\n\n return setUserPreference(type, value)\n .catch(Notification.exception);\n};\n\n/**\n * Event listener for the Display filter (cards, list).\n *\n * @param {object} root The root element for the overview block\n */\nconst registerSelector = root => {\n\n const Selector = root.find(SELECTORS.FILTERS);\n\n CustomEvents.define(Selector, [CustomEvents.events.activate]);\n Selector.on(\n CustomEvents.events.activate,\n SELECTORS.FILTER_OPTION,\n (e, data) => {\n const option = $(e.target);\n\n if (option.hasClass('active')) {\n // If it's already active then we don't need to do anything.\n return;\n }\n\n const filter = option.attr('data-filter');\n const pref = option.attr('data-pref');\n const customfieldvalue = option.attr('data-customfieldvalue');\n\n root.find(SELECTORS.courseView.region).attr('data-' + filter, option.attr('data-value'));\n updatePreferences(filter, pref);\n\n if (customfieldvalue) {\n root.find(SELECTORS.courseView.region).attr('data-customfieldvalue', customfieldvalue);\n updatePreferences('customfieldvalue', customfieldvalue);\n }\n\n // Reset the views.\n\n // Check if the user is currently in a searching state, if so we'll reset it.\n const page = document.querySelector(SELECTORS.region.selectBlock);\n const input = page.querySelector(SELECTORS.region.searchInput);\n if (input.value !== '') {\n const clearIcon = page.querySelector(SELECTORS.region.clearIcon);\n input.value = '';\n // Triggers the init so wont need to call it again.\n View.clearSearch(clearIcon, root);\n } else {\n View.init(root);\n }\n\n data.originalEvent.preventDefault();\n }\n );\n\n Selector.on(\n CustomEvents.events.activate,\n SELECTORS.DISPLAY_OPTION,\n (e, data) => {\n const option = $(e.target);\n\n if (option.hasClass('active')) {\n return;\n }\n\n const filter = option.attr('data-display-option');\n const pref = option.attr('data-pref');\n\n root.find(SELECTORS.courseView.region).attr('data-display', option.attr('data-value'));\n updatePreferences(filter, pref);\n View.reset(root);\n data.originalEvent.preventDefault();\n }\n );\n};\n\n/**\n * Initialise the timeline view navigation by adding event listeners to\n * the navigation elements.\n *\n * @param {object} root The root element for the myoverview block\n */\nexport const init = root => {\n root = $(root);\n registerSelector(root);\n};\n"],"names":["updatePreferences","filter","value","type","catch","Notification","exception","root","Selector","find","SELECTORS","FILTERS","CustomEvents","define","events","activate","on","FILTER_OPTION","e","data","option","target","hasClass","attr","pref","customfieldvalue","courseView","region","page","document","querySelector","selectBlock","input","searchInput","clearIcon","View","clearSearch","init","originalEvent","preventDefault","DISPLAY_OPTION","reset","registerSelector"],"mappings":";;;;;;wTAoCMA,kBAAoB,CAACC,OAAQC,aAC3BC,KAAO,YAEPA,KADW,YAAXF,OACO,wCACW,SAAXA,OACA,wCACW,qBAAXA,OACA,6DAEA,6CAGJ,iCAAkBE,KAAMD,OAC1BE,MAAMC,sBAAaC,0BAiFRC,OAzEKA,CAAAA,aAEfC,SAAWD,KAAKE,KAAKC,mBAAUC,SAErCC,aAAaC,OAAOL,SAAU,CAACI,aAAaE,OAAOC,WACnDP,SAASQ,GACLJ,aAAaE,OAAOC,SACpBL,mBAAUO,eACV,CAACC,EAAGC,cACMC,QAAS,mBAAEF,EAAEG,WAEfD,OAAOE,SAAS,uBAKdrB,OAASmB,OAAOG,KAAK,eACrBC,KAAOJ,OAAOG,KAAK,aACnBE,iBAAmBL,OAAOG,KAAK,yBAErChB,KAAKE,KAAKC,mBAAUgB,WAAWC,QAAQJ,KAAK,QAAUtB,OAAQmB,OAAOG,KAAK,eAC1EvB,kBAAkBC,OAAQuB,MAEtBC,mBACAlB,KAAKE,KAAKC,mBAAUgB,WAAWC,QAAQJ,KAAK,wBAAyBE,kBACrEzB,kBAAkB,mBAAoByB,yBAMpCG,KAAOC,SAASC,cAAcpB,mBAAUiB,OAAOI,aAC/CC,MAAQJ,KAAKE,cAAcpB,mBAAUiB,OAAOM,gBAC9B,KAAhBD,MAAM9B,MAAc,OACdgC,UAAYN,KAAKE,cAAcpB,mBAAUiB,OAAOO,WACtDF,MAAM9B,MAAQ,GAEdiC,KAAKC,YAAYF,UAAW3B,WAE5B4B,KAAKE,KAAK9B,MAGdY,KAAKmB,cAAcC,oBAI3B/B,SAASQ,GACLJ,aAAaE,OAAOC,SACpBL,mBAAU8B,gBACV,CAACtB,EAAGC,cACMC,QAAS,mBAAEF,EAAEG,WAEfD,OAAOE,SAAS,uBAIdrB,OAASmB,OAAOG,KAAK,uBACrBC,KAAOJ,OAAOG,KAAK,aAEzBhB,KAAKE,KAAKC,mBAAUgB,WAAWC,QAAQJ,KAAK,eAAgBH,OAAOG,KAAK,eACxEvB,kBAAkBC,OAAQuB,MAC1BW,KAAKM,MAAMlC,MACXY,KAAKmB,cAAcC,qBAa3BG,CADAnC,MAAO,mBAAEA"}PK'9\#Pp p *myoverview/amd/build/repository.min.js.mapnu[{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to retrieve enrolled coruses from the server.\n *\n * @module block_myoverview/repository\n * @copyright 2018 Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Retrieve a list of enrolled courses.\n *\n * Valid args are:\n * string classification future, inprogress, past\n * int limit number of records to retreive\n * int Offset offset for pagination\n * int sort sort by lastaccess or name\n *\n * @method getEnrolledCoursesByTimeline\n * @param {object} args The request arguments\n * @return {promise} Resolved with an array of courses\n */\nexport const getEnrolledCoursesByTimeline = args => {\n const request = {\n methodname: 'core_course_get_enrolled_courses_by_timeline_classification',\n args: args\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Set the favourite state on a list of courses.\n *\n * Valid args are:\n * Array courses list of course id numbers.\n *\n * @param {Object} args Arguments send to the webservice.\n * @return {Promise} Resolve with warnings.\n */\nexport const setFavouriteCourses = args => {\n const request = {\n methodname: 'core_course_set_favourite_courses',\n args: args\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * These course fields are the only ones needed to be included in the results for the card and list views.\n *\n * @type {string[]}\n */\nexport const CARDLIST_REQUIRED_FIELDS = [\n 'id',\n 'fullname',\n 'shortname',\n 'showcoursecategory',\n 'showshortname',\n 'visible',\n 'enddate',\n];\n\n/**\n * These course fields are the only ones needed to be included in the results for the card and list views.\n *\n * @type {string[]}\n */\nexport const SUMMARY_REQUIRED_FIELDS = [\n 'id',\n 'fullname',\n 'shortname',\n 'showcoursecategory',\n 'showshortname',\n 'visible',\n 'enddate',\n 'summary',\n 'summaryformat',\n];\n"],"names":["args","request","methodname","Ajax","call"],"mappings":";;;;;;;oSAsC4CA,aAClCC,QAAU,CACZC,WAAY,8DACZF,KAAMA,aAGHG,cAAKC,KAAK,CAACH,UAAU,iCAYGD,aACzBC,QAAU,CACZC,WAAY,oCACZF,KAAMA,aAGHG,cAAKC,KAAK,CAACH,UAAU,sCAQQ,CACpC,KACA,WACA,YACA,qBACA,gBACA,UACA,4CAQmC,CACnC,KACA,WACA,YACA,qBACA,gBACA,UACA,UACA,UACA"}PK'9\R+q q )myoverview/amd/build/selectors.min.js.mapnu[{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to initialise the selectors for the myoverview block.\n *\n * @copyright 2018 Peter Dias \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n courseView: {\n region: '[data-region=\"courses-view\"]',\n regionContent: '[data-region=\"course-view-content\"]'\n },\n FILTERS: '[data-region=\"filter\"]',\n FILTER_OPTION: '[data-filter]',\n DISPLAY_OPTION: '[data-display-option]',\n ACTION_HIDE_COURSE: '[data-action=\"hide-course\"]',\n ACTION_SHOW_COURSE: '[data-action=\"show-course\"]',\n ACTION_ADD_FAVOURITE: '[data-action=\"add-favourite\"]',\n ACTION_REMOVE_FAVOURITE: '[data-action=\"remove-favourite\"]',\n FAVOURITE_ICON: '[data-region=\"favourite-icon\"]',\n ICON_IS_FAVOURITE: '[data-region=\"is-favourite\"]',\n ICON_NOT_FAVOURITE: '[data-region=\"not-favourite\"]',\n region: {\n selectBlock: '[data-region=\"myoverview\"]',\n clearIcon: '[data-action=\"clearsearch\"]',\n searchInput: '[data-action=\"search\"]',\n },\n};\n"],"names":["courseView","region","regionContent","FILTERS","FILTER_OPTION","DISPLAY_OPTION","ACTION_HIDE_COURSE","ACTION_SHOW_COURSE","ACTION_ADD_FAVOURITE","ACTION_REMOVE_FAVOURITE","FAVOURITE_ICON","ICON_IS_FAVOURITE","ICON_NOT_FAVOURITE","selectBlock","clearIcon","searchInput"],"mappings":"4KAsBe,CACXA,WAAY,CACRC,OAAQ,+BACRC,cAAe,uCAEnBC,QAAS,yBACTC,cAAe,gBACfC,eAAgB,wBAChBC,mBAAoB,8BACpBC,mBAAoB,8BACpBC,qBAAsB,gCACtBC,wBAAyB,mCACzBC,eAAgB,iCAChBC,kBAAmB,+BACnBC,mBAAoB,gCACpBX,OAAQ,CACJY,YAAa,6BACbC,UAAW,8BACXC,YAAa"}PK'9\/8%myoverview/amd/build/selectors.min.jsnu[define("block_myoverview/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={courseView:{region:'[data-region="courses-view"]',regionContent:'[data-region="course-view-content"]'},FILTERS:'[data-region="filter"]',FILTER_OPTION:"[data-filter]",DISPLAY_OPTION:"[data-display-option]",ACTION_HIDE_COURSE:'[data-action="hide-course"]',ACTION_SHOW_COURSE:'[data-action="show-course"]',ACTION_ADD_FAVOURITE:'[data-action="add-favourite"]',ACTION_REMOVE_FAVOURITE:'[data-action="remove-favourite"]',FAVOURITE_ICON:'[data-region="favourite-icon"]',ICON_IS_FAVOURITE:'[data-region="is-favourite"]',ICON_NOT_FAVOURITE:'[data-region="not-favourite"]',region:{selectBlock:'[data-region="myoverview"]',clearIcon:'[data-action="clearsearch"]',searchInput:'[data-action="search"]'}},_exports.default})); //# sourceMappingURL=selectors.min.js.mapPK'9\y$myoverview/amd/build/main.min.js.mapnu[{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to initialise the myoverview block.\n *\n * @copyright 2018 Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as View from 'block_myoverview/view';\nimport * as ViewNav from 'block_myoverview/view_nav';\n\n/**\n * Initialise all of the modules for the overview block.\n *\n * @param {object} root The root element for the overview block.\n */\nexport const init = (root) => {\n // Initialise the course navigation elements.\n ViewNav.init(root);\n // Initialise the courses view modules.\n View.init(root);\n};\n"],"names":["root","ViewNav","init","View"],"mappings":";;;;;;4KA8BqBA,OAEjBC,QAAQC,KAAKF,MAEbG,KAAKD,KAAKF"}PK'9\-{>$myoverview/amd/build/view_nav.min.jsnu[define("block_myoverview/view_nav",["exports","jquery","core/custom_interaction_events","core/notification","core_user/repository","block_myoverview/view","block_myoverview/selectors"],(function(_exports,_jquery,CustomEvents,_notification,_repository,View,_selectors){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Manage the timeline view navigation for the overview block. * * @copyright 2018 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=_interopRequireDefault(_jquery),CustomEvents=_interopRequireWildcard(CustomEvents),_notification=_interopRequireDefault(_notification),View=_interopRequireWildcard(View),_selectors=_interopRequireDefault(_selectors);const updatePreferences=(filter,value)=>{let type=null;return type="display"===filter?"block_myoverview_user_view_preference":"sort"===filter?"block_myoverview_user_sort_preference":"customfieldvalue"===filter?"block_myoverview_user_grouping_customfieldvalue_preference":"block_myoverview_user_grouping_preference",(0,_repository.setUserPreference)(type,value).catch(_notification.default.exception)};_exports.init=root=>{(root=>{const Selector=root.find(_selectors.default.FILTERS);CustomEvents.define(Selector,[CustomEvents.events.activate]),Selector.on(CustomEvents.events.activate,_selectors.default.FILTER_OPTION,((e,data)=>{const option=(0,_jquery.default)(e.target);if(option.hasClass("active"))return;const filter=option.attr("data-filter"),pref=option.attr("data-pref"),customfieldvalue=option.attr("data-customfieldvalue");root.find(_selectors.default.courseView.region).attr("data-"+filter,option.attr("data-value")),updatePreferences(filter,pref),customfieldvalue&&(root.find(_selectors.default.courseView.region).attr("data-customfieldvalue",customfieldvalue),updatePreferences("customfieldvalue",customfieldvalue));const page=document.querySelector(_selectors.default.region.selectBlock),input=page.querySelector(_selectors.default.region.searchInput);if(""!==input.value){const clearIcon=page.querySelector(_selectors.default.region.clearIcon);input.value="",View.clearSearch(clearIcon,root)}else View.init(root);data.originalEvent.preventDefault()})),Selector.on(CustomEvents.events.activate,_selectors.default.DISPLAY_OPTION,((e,data)=>{const option=(0,_jquery.default)(e.target);if(option.hasClass("active"))return;const filter=option.attr("data-display-option"),pref=option.attr("data-pref");root.find(_selectors.default.courseView.region).attr("data-display",option.attr("data-value")),updatePreferences(filter,pref),View.reset(root),data.originalEvent.preventDefault()}))})(root=(0,_jquery.default)(root))}})); //# sourceMappingURL=view_nav.min.js.mapPK'9\NYP<<$myoverview/amd/build/view.min.js.mapnu[{"version":3,"file":"view.min.js","sources":["../src/view.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Manage the courses view for the overview block.\n *\n * @copyright 2018 Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as Repository from 'block_myoverview/repository';\nimport * as PagedContentFactory from 'core/paged_content_factory';\nimport * as PubSub from 'core/pubsub';\nimport * as CustomEvents from 'core/custom_interaction_events';\nimport * as Notification from 'core/notification';\nimport * as Templates from 'core/templates';\nimport * as CourseEvents from 'core_course/events';\nimport SELECTORS from 'block_myoverview/selectors';\nimport * as PagedContentEvents from 'core/paged_content_events';\nimport * as Aria from 'core/aria';\nimport {debounce} from 'core/utils';\nimport {setUserPreference} from 'core_user/repository';\n\nconst TEMPLATES = {\n COURSES_CARDS: 'block_myoverview/view-cards',\n COURSES_LIST: 'block_myoverview/view-list',\n COURSES_SUMMARY: 'block_myoverview/view-summary',\n NOCOURSES: 'core_course/no-courses'\n};\n\nconst GROUPINGS = {\n GROUPING_ALLINCLUDINGHIDDEN: 'allincludinghidden',\n GROUPING_ALL: 'all',\n GROUPING_INPROGRESS: 'inprogress',\n GROUPING_FUTURE: 'future',\n GROUPING_PAST: 'past',\n GROUPING_FAVOURITES: 'favourites',\n GROUPING_HIDDEN: 'hidden'\n};\n\nconst NUMCOURSES_PERPAGE = [12, 24, 48, 96, 0];\n\nlet loadedPages = [];\n\nlet courseOffset = 0;\n\nlet lastPage = 0;\n\nlet lastLimit = 0;\n\nlet namespace = null;\n\n/**\n * Whether the summary display has been loaded.\n *\n * If true, this means that courses have been loaded with the summary text.\n * Otherwise, switching to the summary display mode will require course data to be fetched with the summary text.\n *\n * @type {boolean}\n */\nlet summaryDisplayLoaded = false;\n\n/**\n * Get filter values from DOM.\n *\n * @param {object} root The root element for the courses view.\n * @return {filters} Set filters.\n */\nconst getFilterValues = root => {\n const courseRegion = root.find(SELECTORS.courseView.region);\n return {\n display: courseRegion.attr('data-display'),\n grouping: courseRegion.attr('data-grouping'),\n sort: courseRegion.attr('data-sort'),\n displaycategories: courseRegion.attr('data-displaycategories'),\n customfieldname: courseRegion.attr('data-customfieldname'),\n customfieldvalue: courseRegion.attr('data-customfieldvalue'),\n };\n};\n\n// We want the paged content controls below the paged content area.\n// and the controls should be ignored while data is loading.\nconst DEFAULT_PAGED_CONTENT_CONFIG = {\n ignoreControlWhileLoading: true,\n controlPlacementBottom: true,\n persistentLimitKey: 'block_myoverview_user_paging_preference'\n};\n\n/**\n * Get enrolled courses from backend.\n *\n * @param {object} filters The filters for this view.\n * @param {int} limit The number of courses to show.\n * @return {promise} Resolved with an array of courses.\n */\nconst getMyCourses = (filters, limit) => {\n const params = {\n offset: courseOffset,\n limit: limit,\n classification: filters.grouping,\n sort: filters.sort,\n customfieldname: filters.customfieldname,\n customfieldvalue: filters.customfieldvalue,\n };\n if (filters.display === 'summary') {\n params.requiredfields = Repository.SUMMARY_REQUIRED_FIELDS;\n summaryDisplayLoaded = true;\n } else {\n params.requiredfields = Repository.CARDLIST_REQUIRED_FIELDS;\n }\n return Repository.getEnrolledCoursesByTimeline(params);\n};\n\n/**\n * Search for enrolled courses from backend.\n *\n * @param {object} filters The filters for this view.\n * @param {int} limit The number of courses to show.\n * @param {string} searchValue What does the user want to search within their courses.\n * @return {promise} Resolved with an array of courses.\n */\nconst getSearchMyCourses = (filters, limit, searchValue) => {\n const params = {\n offset: courseOffset,\n limit: limit,\n classification: 'search',\n sort: filters.sort,\n customfieldname: filters.customfieldname,\n customfieldvalue: filters.customfieldvalue,\n searchvalue: searchValue,\n };\n if (filters.display === 'summary') {\n params.requiredfields = Repository.SUMMARY_REQUIRED_FIELDS;\n summaryDisplayLoaded = true;\n } else {\n params.requiredfields = Repository.CARDLIST_REQUIRED_FIELDS;\n summaryDisplayLoaded = false;\n }\n return Repository.getEnrolledCoursesByTimeline(params);\n};\n\n/**\n * Get the container element for the favourite icon.\n *\n * @param {Object} root The course overview container\n * @param {Number} courseId Course id number\n * @return {Object} The favourite icon container\n */\nconst getFavouriteIconContainer = (root, courseId) => {\n return root.find(SELECTORS.FAVOURITE_ICON + '[data-course-id=\"' + courseId + '\"]');\n};\n\n/**\n * Get the paged content container element.\n *\n * @param {Object} root The course overview container\n * @param {Number} index Rendered page index.\n * @return {Object} The rendered paged container.\n */\nconst getPagedContentContainer = (root, index) => {\n return root.find('[data-region=\"paged-content-page\"][data-page=\"' + index + '\"]');\n};\n\n/**\n * Get the course id from a favourite element.\n *\n * @param {Object} root The favourite icon container element.\n * @return {Number} Course id.\n */\nconst getCourseId = root => {\n return root.attr('data-course-id');\n};\n\n/**\n * Hide the favourite icon.\n *\n * @param {Object} root The favourite icon container element.\n * @param {Number} courseId Course id number.\n */\nconst hideFavouriteIcon = (root, courseId) => {\n const iconContainer = getFavouriteIconContainer(root, courseId);\n\n const isFavouriteIcon = iconContainer.find(SELECTORS.ICON_IS_FAVOURITE);\n isFavouriteIcon.addClass('hidden');\n Aria.hide(isFavouriteIcon);\n\n const notFavourteIcon = iconContainer.find(SELECTORS.ICON_NOT_FAVOURITE);\n notFavourteIcon.removeClass('hidden');\n Aria.unhide(notFavourteIcon);\n};\n\n/**\n * Show the favourite icon.\n *\n * @param {Object} root The course overview container.\n * @param {Number} courseId Course id number.\n */\nconst showFavouriteIcon = (root, courseId) => {\n const iconContainer = getFavouriteIconContainer(root, courseId);\n\n const isFavouriteIcon = iconContainer.find(SELECTORS.ICON_IS_FAVOURITE);\n isFavouriteIcon.removeClass('hidden');\n Aria.unhide(isFavouriteIcon);\n\n const notFavourteIcon = iconContainer.find(SELECTORS.ICON_NOT_FAVOURITE);\n notFavourteIcon.addClass('hidden');\n Aria.hide(notFavourteIcon);\n};\n\n/**\n * Get the action menu item\n *\n * @param {Object} root The course overview container\n * @param {Number} courseId Course id.\n * @return {Object} The add to favourite menu item.\n */\nconst getAddFavouriteMenuItem = (root, courseId) => {\n return root.find('[data-action=\"add-favourite\"][data-course-id=\"' + courseId + '\"]');\n};\n\n/**\n * Get the action menu item\n *\n * @param {Object} root The course overview container\n * @param {Number} courseId Course id.\n * @return {Object} The remove from favourites menu item.\n */\nconst getRemoveFavouriteMenuItem = (root, courseId) => {\n return root.find('[data-action=\"remove-favourite\"][data-course-id=\"' + courseId + '\"]');\n};\n\n/**\n * Add course to favourites\n *\n * @param {Object} root The course overview container\n * @param {Number} courseId Course id number\n */\nconst addToFavourites = (root, courseId) => {\n const removeAction = getRemoveFavouriteMenuItem(root, courseId);\n const addAction = getAddFavouriteMenuItem(root, courseId);\n\n setCourseFavouriteState(courseId, true).then(success => {\n if (success) {\n PubSub.publish(CourseEvents.favourited, courseId);\n removeAction.removeClass('hidden');\n addAction.addClass('hidden');\n showFavouriteIcon(root, courseId);\n } else {\n Notification.alert('Starring course failed', 'Could not change favourite state');\n }\n return;\n }).catch(Notification.exception);\n};\n\n/**\n * Remove course from favourites\n *\n * @param {Object} root The course overview container\n * @param {Number} courseId Course id number\n */\nconst removeFromFavourites = (root, courseId) => {\n const removeAction = getRemoveFavouriteMenuItem(root, courseId);\n const addAction = getAddFavouriteMenuItem(root, courseId);\n\n setCourseFavouriteState(courseId, false).then(success => {\n if (success) {\n PubSub.publish(CourseEvents.unfavorited, courseId);\n removeAction.addClass('hidden');\n addAction.removeClass('hidden');\n hideFavouriteIcon(root, courseId);\n } else {\n Notification.alert('Starring course failed', 'Could not change favourite state');\n }\n return;\n }).catch(Notification.exception);\n};\n\n/**\n * Get the action menu item\n *\n * @param {Object} root The course overview container\n * @param {Number} courseId Course id.\n * @return {Object} The hide course menu item.\n */\nconst getHideCourseMenuItem = (root, courseId) => {\n return root.find('[data-action=\"hide-course\"][data-course-id=\"' + courseId + '\"]');\n};\n\n/**\n * Get the action menu item\n *\n * @param {Object} root The course overview container\n * @param {Number} courseId Course id.\n * @return {Object} The show course menu item.\n */\nconst getShowCourseMenuItem = (root, courseId) => {\n return root.find('[data-action=\"show-course\"][data-course-id=\"' + courseId + '\"]');\n};\n\n/**\n * Hide course\n *\n * @param {Object} root The course overview container\n * @param {Number} courseId Course id number\n */\nconst hideCourse = (root, courseId) => {\n const hideAction = getHideCourseMenuItem(root, courseId);\n const showAction = getShowCourseMenuItem(root, courseId);\n const filters = getFilterValues(root);\n\n setCourseHiddenState(courseId, true);\n\n // Remove the course from this view as it is now hidden and thus not covered by this view anymore.\n // Do only if we are not in \"All (including archived)\" view mode where really all courses are shown.\n if (filters.grouping !== GROUPINGS.GROUPING_ALLINCLUDINGHIDDEN) {\n hideElement(root, courseId);\n }\n\n hideAction.addClass('hidden');\n showAction.removeClass('hidden');\n};\n\n/**\n * Show course\n *\n * @param {Object} root The course overview container\n * @param {Number} courseId Course id number\n */\nconst showCourse = (root, courseId) => {\n const hideAction = getHideCourseMenuItem(root, courseId);\n const showAction = getShowCourseMenuItem(root, courseId);\n const filters = getFilterValues(root);\n\n setCourseHiddenState(courseId, null);\n\n // Remove the course from this view as it is now shown again and thus not covered by this view anymore.\n // Do only if we are not in \"All (including archived)\" view mode where really all courses are shown.\n if (filters.grouping !== GROUPINGS.GROUPING_ALLINCLUDINGHIDDEN) {\n hideElement(root, courseId);\n }\n\n hideAction.removeClass('hidden');\n showAction.addClass('hidden');\n};\n\n/**\n * Set the courses hidden status and push to repository\n *\n * @param {Number} courseId Course id to favourite.\n * @param {Boolean} status new hidden status.\n * @return {Promise} Repository promise.\n */\nconst setCourseHiddenState = (courseId, status) => {\n\n // If the given status is not hidden, the preference has to be deleted with a null value.\n if (status === false) {\n status = null;\n }\n\n return setUserPreference(`block_myoverview_hidden_course_${courseId}`, status)\n .catch(Notification.exception);\n};\n\n/**\n * Reset the loadedPages dataset to take into account the hidden element\n *\n * @param {Object} root The course overview container\n * @param {Number} id The course id number\n */\nconst hideElement = (root, id) => {\n const pagingBar = root.find('[data-region=\"paging-bar\"]');\n const jumpto = parseInt(pagingBar.attr('data-active-page-number'));\n\n // Get a reduced dataset for the current page.\n const courseList = loadedPages[jumpto];\n let reducedCourse = courseList.courses.reduce((accumulator, current) => {\n if (+id !== +current.id) {\n accumulator.push(current);\n }\n return accumulator;\n }, []);\n\n // Get the next page's data if loaded and pop the first element from it.\n if (typeof (loadedPages[jumpto + 1]) !== 'undefined') {\n const newElement = loadedPages[jumpto + 1].courses.slice(0, 1);\n\n // Adjust the dataset for the reset of the pages that are loaded.\n loadedPages.forEach((courseList, index) => {\n if (index > jumpto) {\n let popElement = [];\n if (typeof (loadedPages[index + 1]) !== 'undefined') {\n popElement = loadedPages[index + 1].courses.slice(0, 1);\n }\n loadedPages[index].courses = [...loadedPages[index].courses.slice(1), ...popElement];\n }\n });\n\n reducedCourse = [...reducedCourse, ...newElement];\n }\n\n // Check if the next page is the last page and if it still has data associated to it.\n if (lastPage === jumpto + 1 && loadedPages[jumpto + 1].courses.length === 0) {\n const pagedContentContainer = root.find('[data-region=\"paged-content-container\"]');\n PagedContentFactory.resetLastPageNumber($(pagedContentContainer).attr('id'), jumpto);\n }\n\n loadedPages[jumpto].courses = reducedCourse;\n\n // Reduce the course offset.\n courseOffset--;\n\n // Render the paged content for the current.\n const pagedContentPage = getPagedContentContainer(root, jumpto);\n renderCourses(root, loadedPages[jumpto]).then((html, js) => {\n return Templates.replaceNodeContents(pagedContentPage, html, js);\n }).catch(Notification.exception);\n\n // Delete subsequent pages in order to trigger the callback.\n loadedPages.forEach((courseList, index) => {\n if (index > jumpto) {\n const page = getPagedContentContainer(root, index);\n page.remove();\n }\n });\n};\n\n/**\n * Set the courses favourite status and push to repository\n *\n * @param {Number} courseId Course id to favourite.\n * @param {boolean} status new favourite status.\n * @return {Promise} Repository promise.\n */\nconst setCourseFavouriteState = (courseId, status) => {\n\n return Repository.setFavouriteCourses({\n courses: [\n {\n 'id': courseId,\n 'favourite': status\n }\n ]\n }).then(result => {\n if (result.warnings.length === 0) {\n loadedPages.forEach(courseList => {\n courseList.courses.forEach((course, index) => {\n if (course.id == courseId) {\n courseList.courses[index].isfavourite = status;\n }\n });\n });\n return true;\n } else {\n return false;\n }\n }).catch(Notification.exception);\n};\n\n/**\n * Given there are no courses to render provide the rendered template.\n *\n * @param {object} root The root element for the courses view.\n * @return {promise} jQuery promise resolved after rendering is complete.\n */\nconst noCoursesRender = root => {\n const nocoursesimg = root.find(SELECTORS.courseView.region).attr('data-nocoursesimg');\n const newcourseurl = root.find(SELECTORS.courseView.region).attr('data-newcourseurl');\n return Templates.render(TEMPLATES.NOCOURSES, {\n nocoursesimg: nocoursesimg,\n newcourseurl: newcourseurl\n });\n};\n\n/**\n * Render the dashboard courses.\n *\n * @param {object} root The root element for the courses view.\n * @param {array} coursesData containing array of returned courses.\n * @return {promise} jQuery promise resolved after rendering is complete.\n */\nconst renderCourses = (root, coursesData) => {\n\n const filters = getFilterValues(root);\n\n let currentTemplate = '';\n if (filters.display === 'card') {\n currentTemplate = TEMPLATES.COURSES_CARDS;\n } else if (filters.display === 'list') {\n currentTemplate = TEMPLATES.COURSES_LIST;\n } else {\n currentTemplate = TEMPLATES.COURSES_SUMMARY;\n }\n\n if (!coursesData) {\n return noCoursesRender(root);\n } else {\n // Sometimes we get weird objects coming after a failed search, cast to ensure typing functions.\n if (Array.isArray(coursesData.courses) === false) {\n coursesData.courses = Object.values(coursesData.courses);\n }\n // Whether the course category should be displayed in the course item.\n coursesData.courses = coursesData.courses.map(course => {\n course.showcoursecategory = filters.displaycategories === 'on';\n return course;\n });\n if (coursesData.courses.length) {\n return Templates.render(currentTemplate, {\n courses: coursesData.courses,\n });\n } else {\n return noCoursesRender(root);\n }\n }\n};\n\n/**\n * Return the callback to be passed to the subscribe event\n *\n * @param {object} root The root element for the courses view\n * @return {function} Partially applied function that'll execute when passed a limit\n */\nconst setLimit = root => {\n // @param {Number} limit The paged limit that is passed through the event.\n return limit => root.find(SELECTORS.courseView.region).attr('data-paging', limit);\n};\n\n/**\n * Intialise the paged list and cards views on page load.\n * Returns an array of paged contents that we would like to handle here\n *\n * @param {object} root The root element for the courses view\n * @param {string} namespace The namespace for all the events attached\n */\nconst registerPagedEventHandlers = (root, namespace) => {\n const event = namespace + PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT;\n PubSub.subscribe(event, setLimit(root));\n};\n\n/**\n * Figure out how many items are going to be allowed to be rendered in the block.\n *\n * @param {Number} pagingLimit How many courses to display\n * @param {Object} root The course overview container\n * @return {Number[]} How many courses will be rendered\n */\nconst itemsPerPageFunc = (pagingLimit, root) => {\n let itemsPerPage = NUMCOURSES_PERPAGE.map(value => {\n let active = false;\n if (value === pagingLimit) {\n active = true;\n }\n\n return {\n value: value,\n active: active\n };\n });\n\n // Filter out all pagination options which are too large for the amount of courses user is enrolled in.\n const totalCourseCount = parseInt(root.find(SELECTORS.courseView.region).attr('data-totalcoursecount'), 10);\n return itemsPerPage.filter(pagingOption => {\n if (pagingOption.value === 0 && totalCourseCount > 100) {\n // To minimise performance issues, do not show the \"All\" option if the user is enrolled in more than 100 courses.\n return false;\n }\n return pagingOption.value < totalCourseCount;\n });\n};\n\n/**\n * Mutates and controls the loadedPages array and handles the bootstrapping.\n *\n * @param {Array|Object} coursesData Array of all of the courses to start building the page from\n * @param {Number} currentPage What page are we currently on?\n * @param {Object} pageData Any current page information\n * @param {Object} actions Paged content helper\n * @param {null|boolean} activeSearch Are we currently actively searching and building up search results?\n */\nconst pageBuilder = (coursesData, currentPage, pageData, actions, activeSearch = null) => {\n // If the courseData comes in an object then get the value otherwise it is a pure array.\n let courses = coursesData.courses ? coursesData.courses : coursesData;\n let nextPageStart = 0;\n let pageCourses = [];\n\n // If current page's data is loaded make sure we max it to page limit.\n if (typeof (loadedPages[currentPage]) !== 'undefined') {\n pageCourses = loadedPages[currentPage].courses;\n const currentPageLength = pageCourses.length;\n if (currentPageLength < pageData.limit) {\n nextPageStart = pageData.limit - currentPageLength;\n pageCourses = {...loadedPages[currentPage].courses, ...courses.slice(0, nextPageStart)};\n }\n } else {\n // When the page limit is zero, there is only one page of courses, no start for next page.\n nextPageStart = pageData.limit || false;\n pageCourses = (pageData.limit > 0) ? courses.slice(0, pageData.limit) : courses;\n }\n\n // Finished setting up the current page.\n loadedPages[currentPage] = {\n courses: pageCourses\n };\n\n // Set up the next page (if there is more than one page).\n const remainingCourses = nextPageStart !== false ? courses.slice(nextPageStart, courses.length) : [];\n if (remainingCourses.length) {\n loadedPages[currentPage + 1] = {\n courses: remainingCourses\n };\n }\n\n // Set the last page to either the current or next page.\n if (loadedPages[currentPage].courses.length < pageData.limit || !remainingCourses.length) {\n lastPage = currentPage;\n if (activeSearch === null) {\n actions.allItemsLoaded(currentPage);\n }\n } else if (typeof (loadedPages[currentPage + 1]) !== 'undefined'\n && loadedPages[currentPage + 1].courses.length < pageData.limit) {\n lastPage = currentPage + 1;\n }\n\n courseOffset = coursesData.nextoffset;\n};\n\n/**\n * In cases when switching between regular rendering and search rendering we need to reset some variables.\n */\nconst resetGlobals = () => {\n courseOffset = 0;\n loadedPages = [];\n lastPage = 0;\n lastLimit = 0;\n};\n\n/**\n * The default functionality of fetching paginated courses without special handling.\n *\n * @return {function(Object, Object, Object, Object, Object, Promise, Number): void}\n */\nconst standardFunctionalityCurry = () => {\n resetGlobals();\n return (filters, currentPage, pageData, actions, root, promises, limit) => {\n const pagePromise = getMyCourses(\n filters,\n limit\n ).then(coursesData => {\n pageBuilder(coursesData, currentPage, pageData, actions);\n return renderCourses(root, loadedPages[currentPage]);\n }).catch(Notification.exception);\n\n promises.push(pagePromise);\n };\n};\n\n/**\n * Initialize the searching functionality so we can call it when required.\n *\n * @return {function(Object, Number, Object, Object, Object, Promise, Number, String): void}\n */\nconst searchFunctionalityCurry = () => {\n resetGlobals();\n return (filters, currentPage, pageData, actions, root, promises, limit, inputValue) => {\n const searchingPromise = getSearchMyCourses(\n filters,\n limit,\n inputValue\n ).then(coursesData => {\n pageBuilder(coursesData, currentPage, pageData, actions);\n return renderCourses(root, loadedPages[currentPage]);\n }).catch(Notification.exception);\n\n promises.push(searchingPromise);\n };\n};\n\n/**\n * Initialise the courses list and cards views on page load.\n *\n * @param {object} root The root element for the courses view.\n * @param {function} promiseFunction How do we fetch the courses and what do we do with them?\n * @param {null | string} inputValue What to search for\n */\nconst initializePagedContent = (root, promiseFunction, inputValue = null) => {\n const pagingLimit = parseInt(root.find(SELECTORS.courseView.region).attr('data-paging'), 10);\n let itemsPerPage = itemsPerPageFunc(pagingLimit, root);\n\n const config = {...{}, ...DEFAULT_PAGED_CONTENT_CONFIG};\n config.eventNamespace = namespace;\n\n const pagedContentPromise = PagedContentFactory.createWithLimit(\n itemsPerPage,\n (pagesData, actions) => {\n let promises = [];\n pagesData.forEach(pageData => {\n const currentPage = pageData.pageNumber;\n let limit = (pageData.limit > 0) ? pageData.limit : 0;\n\n // Reset local variables if limits have changed.\n if (+lastLimit !== +limit) {\n loadedPages = [];\n courseOffset = 0;\n lastPage = 0;\n }\n\n if (lastPage === currentPage) {\n // If we are on the last page and have it's data then load it from cache.\n actions.allItemsLoaded(lastPage);\n promises.push(renderCourses(root, loadedPages[currentPage]));\n return;\n }\n\n lastLimit = limit;\n\n // Get 2 pages worth of data as we will need it for the hidden functionality.\n if (typeof (loadedPages[currentPage + 1]) === 'undefined') {\n if (typeof (loadedPages[currentPage]) === 'undefined') {\n limit *= 2;\n }\n }\n\n // Get the current applied filters.\n const filters = getFilterValues(root);\n\n // Call the curried function that'll handle the course promise and any manipulation of it.\n promiseFunction(filters, currentPage, pageData, actions, root, promises, limit, inputValue);\n });\n return promises;\n },\n config\n );\n\n pagedContentPromise.then((html, js) => {\n registerPagedEventHandlers(root, namespace);\n return Templates.replaceNodeContents(root.find(SELECTORS.courseView.region), html, js);\n }).catch(Notification.exception);\n};\n\n/**\n * Listen to, and handle events for the myoverview block.\n *\n * @param {Object} root The myoverview block container element.\n * @param {HTMLElement} page The whole HTMLElement for our block.\n */\nconst registerEventListeners = (root, page) => {\n\n CustomEvents.define(root, [\n CustomEvents.events.activate\n ]);\n\n root.on(CustomEvents.events.activate, SELECTORS.ACTION_ADD_FAVOURITE, (e, data) => {\n const favourite = $(e.target).closest(SELECTORS.ACTION_ADD_FAVOURITE);\n const courseId = getCourseId(favourite);\n addToFavourites(root, courseId);\n data.originalEvent.preventDefault();\n });\n\n root.on(CustomEvents.events.activate, SELECTORS.ACTION_REMOVE_FAVOURITE, (e, data) => {\n const favourite = $(e.target).closest(SELECTORS.ACTION_REMOVE_FAVOURITE);\n const courseId = getCourseId(favourite);\n removeFromFavourites(root, courseId);\n data.originalEvent.preventDefault();\n });\n\n root.on(CustomEvents.events.activate, SELECTORS.FAVOURITE_ICON, (e, data) => {\n data.originalEvent.preventDefault();\n });\n\n root.on(CustomEvents.events.activate, SELECTORS.ACTION_HIDE_COURSE, (e, data) => {\n const target = $(e.target).closest(SELECTORS.ACTION_HIDE_COURSE);\n const courseId = getCourseId(target);\n hideCourse(root, courseId);\n data.originalEvent.preventDefault();\n });\n\n root.on(CustomEvents.events.activate, SELECTORS.ACTION_SHOW_COURSE, (e, data) => {\n const target = $(e.target).closest(SELECTORS.ACTION_SHOW_COURSE);\n const courseId = getCourseId(target);\n showCourse(root, courseId);\n data.originalEvent.preventDefault();\n });\n\n // Searching functionality event handlers.\n const input = page.querySelector(SELECTORS.region.searchInput);\n const clearIcon = page.querySelector(SELECTORS.region.clearIcon);\n\n clearIcon.addEventListener('click', () => {\n input.value = '';\n input.focus();\n clearSearch(clearIcon, root);\n });\n\n input.addEventListener('input', debounce(() => {\n if (input.value === '') {\n clearSearch(clearIcon, root);\n } else {\n activeSearch(clearIcon);\n initializePagedContent(root, searchFunctionalityCurry(), input.value.trim());\n }\n }, 1000));\n};\n\n/**\n * Reset the search icon and trigger the init for the block.\n *\n * @param {HTMLElement} clearIcon Our closing icon to manipulate.\n * @param {Object} root The myoverview block container element.\n */\nexport const clearSearch = (clearIcon, root) => {\n clearIcon.classList.add('d-none');\n init(root);\n};\n\n/**\n * Change the searching icon to its' active state.\n *\n * @param {HTMLElement} clearIcon Our closing icon to manipulate.\n */\nconst activeSearch = (clearIcon) => {\n clearIcon.classList.remove('d-none');\n};\n\n/**\n * Intialise the courses list and cards views on page load.\n *\n * @param {object} root The root element for the courses view.\n */\nexport const init = root => {\n root = $(root);\n loadedPages = [];\n lastPage = 0;\n courseOffset = 0;\n\n if (!root.attr('data-init')) {\n const page = document.querySelector(SELECTORS.region.selectBlock);\n registerEventListeners(root, page);\n namespace = \"block_myoverview_\" + root.attr('id') + \"_\" + Math.random();\n root.attr('data-init', true);\n }\n\n initializePagedContent(root, standardFunctionalityCurry());\n};\n\n/**\n * Reset the courses views to their original\n * state on first page load.courseOffset\n *\n * This is called when configuration has changed for the event lists\n * to cause them to reload their data.\n *\n * @param {Object} root The root element for the timeline view.\n */\nexport const reset = root => {\n if (loadedPages.length > 0) {\n const filters = getFilterValues(root);\n // If the display mode is changed to 'summary' but the summary display has not been loaded yet,\n // we need to re-fetch the courses to include the course summary text.\n if (filters.display === 'summary' && !summaryDisplayLoaded) {\n const page = document.querySelector(SELECTORS.region.selectBlock);\n const input = page.querySelector(SELECTORS.region.searchInput);\n if (input.value !== '') {\n initializePagedContent(root, searchFunctionalityCurry(), input.value.trim());\n } else {\n initializePagedContent(root, standardFunctionalityCurry());\n }\n } else {\n loadedPages.forEach((courseList, index) => {\n let pagedContentPage = getPagedContentContainer(root, index);\n renderCourses(root, courseList).then((html, js) => {\n return Templates.replaceNodeContents(pagedContentPage, html, js);\n }).catch(Notification.exception);\n });\n }\n } else {\n init(root);\n }\n};\n"],"names":["TEMPLATES","GROUPINGS","NUMCOURSES_PERPAGE","loadedPages","courseOffset","lastPage","lastLimit","namespace","summaryDisplayLoaded","getFilterValues","root","courseRegion","find","SELECTORS","courseView","region","display","attr","grouping","sort","displaycategories","customfieldname","customfieldvalue","DEFAULT_PAGED_CONTENT_CONFIG","ignoreControlWhileLoading","controlPlacementBottom","persistentLimitKey","getFavouriteIconContainer","courseId","FAVOURITE_ICON","getPagedContentContainer","index","getCourseId","getAddFavouriteMenuItem","getRemoveFavouriteMenuItem","addToFavourites","removeAction","addAction","setCourseFavouriteState","then","success","PubSub","publish","CourseEvents","favourited","removeClass","addClass","iconContainer","isFavouriteIcon","ICON_IS_FAVOURITE","Aria","unhide","notFavourteIcon","ICON_NOT_FAVOURITE","hide","showFavouriteIcon","Notification","alert","catch","exception","removeFromFavourites","unfavorited","hideFavouriteIcon","getHideCourseMenuItem","getShowCourseMenuItem","setCourseHiddenState","status","hideElement","id","pagingBar","jumpto","parseInt","reducedCourse","courses","reduce","accumulator","current","push","newElement","slice","forEach","courseList","popElement","length","pagedContentContainer","PagedContentFactory","resetLastPageNumber","pagedContentPage","renderCourses","html","js","Templates","replaceNodeContents","remove","Repository","setFavouriteCourses","result","warnings","course","isfavourite","noCoursesRender","nocoursesimg","newcourseurl","render","coursesData","filters","currentTemplate","Array","isArray","Object","values","map","showcoursecategory","registerPagedEventHandlers","event","PagedContentEvents","SET_ITEMS_PER_PAGE_LIMIT","subscribe","limit","setLimit","itemsPerPageFunc","pagingLimit","itemsPerPage","value","active","totalCourseCount","filter","pagingOption","pageBuilder","currentPage","pageData","actions","activeSearch","nextPageStart","pageCourses","currentPageLength","remainingCourses","allItemsLoaded","nextoffset","resetGlobals","standardFunctionalityCurry","promises","pagePromise","params","offset","classification","requiredfields","SUMMARY_REQUIRED_FIELDS","CARDLIST_REQUIRED_FIELDS","getEnrolledCoursesByTimeline","getMyCourses","searchFunctionalityCurry","inputValue","searchingPromise","searchValue","searchvalue","getSearchMyCourses","initializePagedContent","promiseFunction","config","eventNamespace","pagedContentPromise","createWithLimit","pagesData","pageNumber","registerEventListeners","page","CustomEvents","define","events","activate","on","ACTION_ADD_FAVOURITE","e","data","favourite","target","closest","originalEvent","preventDefault","ACTION_REMOVE_FAVOURITE","ACTION_HIDE_COURSE","hideAction","showAction","hideCourse","ACTION_SHOW_COURSE","showCourse","input","querySelector","searchInput","clearIcon","addEventListener","focus","clearSearch","trim","classList","add","init","document","selectBlock","Math","random"],"mappings":";;;;;;ipBAoCMA,wBACa,8BADbA,uBAEY,6BAFZA,0BAGe,gCAHfA,oBAIS,yBAGTC,sCAC2B,qBAS3BC,mBAAqB,CAAC,GAAI,GAAI,GAAI,GAAI,OAExCC,YAAc,GAEdC,aAAe,EAEfC,SAAW,EAEXC,UAAY,EAEZC,UAAY,KAUZC,sBAAuB,QAQrBC,gBAAkBC,aACdC,aAAeD,KAAKE,KAAKC,mBAAUC,WAAWC,cAC7C,CACHC,QAASL,aAAaM,KAAK,gBAC3BC,SAAUP,aAAaM,KAAK,iBAC5BE,KAAMR,aAAaM,KAAK,aACxBG,kBAAmBT,aAAaM,KAAK,0BACrCI,gBAAiBV,aAAaM,KAAK,wBACnCK,iBAAkBX,aAAaM,KAAK,2BAMtCM,6BAA+B,CACjCC,2BAA2B,EAC3BC,wBAAwB,EACxBC,mBAAoB,2CA+DlBC,0BAA4B,CAACjB,KAAMkB,WAC9BlB,KAAKE,KAAKC,mBAAUgB,eAAiB,oBAAsBD,SAAW,MAU3EE,yBAA2B,CAACpB,KAAMqB,QAC7BrB,KAAKE,KAAK,iDAAmDmB,MAAQ,MAS1EC,YAActB,MACTA,KAAKO,KAAK,kBA8CfgB,wBAA0B,CAACvB,KAAMkB,WAC5BlB,KAAKE,KAAK,iDAAmDgB,SAAW,MAU7EM,2BAA6B,CAACxB,KAAMkB,WAC/BlB,KAAKE,KAAK,oDAAsDgB,SAAW,MAShFO,gBAAkB,CAACzB,KAAMkB,kBACrBQ,aAAeF,2BAA2BxB,KAAMkB,UAChDS,UAAYJ,wBAAwBvB,KAAMkB,UAEhDU,wBAAwBV,UAAU,GAAMW,MAAKC,UACrCA,SACAC,OAAOC,QAAQC,aAAaC,WAAYhB,UACxCQ,aAAaS,YAAY,UACzBR,UAAUS,SAAS,UAhDL,EAACpC,KAAMkB,kBACvBmB,cAAgBpB,0BAA0BjB,KAAMkB,UAEhDoB,gBAAkBD,cAAcnC,KAAKC,mBAAUoC,mBACrDD,gBAAgBH,YAAY,UAC5BK,KAAKC,OAAOH,uBAENI,gBAAkBL,cAAcnC,KAAKC,mBAAUwC,oBACrDD,gBAAgBN,SAAS,UACzBI,KAAKI,KAAKF,kBAwCFG,CAAkB7C,KAAMkB,WAExB4B,aAAaC,MAAM,yBAA0B,uCAGlDC,MAAMF,aAAaG,YASpBC,qBAAuB,CAAClD,KAAMkB,kBAC1BQ,aAAeF,2BAA2BxB,KAAMkB,UAChDS,UAAYJ,wBAAwBvB,KAAMkB,UAEhDU,wBAAwBV,UAAU,GAAOW,MAAKC,UACtCA,SACAC,OAAOC,QAAQC,aAAakB,YAAajC,UACzCQ,aAAaU,SAAS,UACtBT,UAAUQ,YAAY,UAzFR,EAACnC,KAAMkB,kBACvBmB,cAAgBpB,0BAA0BjB,KAAMkB,UAEhDoB,gBAAkBD,cAAcnC,KAAKC,mBAAUoC,mBACrDD,gBAAgBF,SAAS,UACzBI,KAAKI,KAAKN,uBAEJI,gBAAkBL,cAAcnC,KAAKC,mBAAUwC,oBACrDD,gBAAgBP,YAAY,UAC5BK,KAAKC,OAAOC,kBAiFJU,CAAkBpD,KAAMkB,WAExB4B,aAAaC,MAAM,yBAA0B,uCAGlDC,MAAMF,aAAaG,YAUpBI,sBAAwB,CAACrD,KAAMkB,WAC1BlB,KAAKE,KAAK,+CAAiDgB,SAAW,MAU3EoC,sBAAwB,CAACtD,KAAMkB,WAC1BlB,KAAKE,KAAK,+CAAiDgB,SAAW,MAwD3EqC,qBAAuB,CAACrC,SAAUsC,WAGrB,IAAXA,SACAA,OAAS,OAGN,2EAAoDtC,UAAYsC,QAClER,MAAMF,aAAaG,YAStBQ,YAAc,CAACzD,KAAM0D,YACjBC,UAAY3D,KAAKE,KAAK,8BACtB0D,OAASC,SAASF,UAAUpD,KAAK,gCAInCuD,cADerE,YAAYmE,QACAG,QAAQC,QAAO,CAACC,YAAaC,YACnDR,KAAQQ,QAAQR,IACjBO,YAAYE,KAAKD,SAEdD,cACR,YAGsC,IAA7BxE,YAAYmE,OAAS,GAAqB,OAC5CQ,WAAa3E,YAAYmE,OAAS,GAAGG,QAAQM,MAAM,EAAG,GAG5D5E,YAAY6E,SAAQ,CAACC,WAAYlD,YACzBA,MAAQuC,OAAQ,KACZY,WAAa,QACuB,IAA5B/E,YAAY4B,MAAQ,KAC5BmD,WAAa/E,YAAY4B,MAAQ,GAAG0C,QAAQM,MAAM,EAAG,IAEzD5E,YAAY4B,OAAO0C,QAAU,IAAItE,YAAY4B,OAAO0C,QAAQM,MAAM,MAAOG,gBAIjFV,cAAgB,IAAIA,iBAAkBM,eAItCzE,WAAaiE,OAAS,GAAgD,IAA3CnE,YAAYmE,OAAS,GAAGG,QAAQU,OAAc,OACnEC,sBAAwB1E,KAAKE,KAAK,2CACxCyE,oBAAoBC,qBAAoB,mBAAEF,uBAAuBnE,KAAK,MAAOqD,QAGjFnE,YAAYmE,QAAQG,QAAUD,cAG9BpE,qBAGMmF,iBAAmBzD,yBAAyBpB,KAAM4D,QACxDkB,cAAc9E,KAAMP,YAAYmE,SAAS/B,MAAK,CAACkD,KAAMC,KAC1CC,UAAUC,oBAAoBL,iBAAkBE,KAAMC,MAC9DhC,MAAMF,aAAaG,WAGtBxD,YAAY6E,SAAQ,CAACC,WAAYlD,YACzBA,MAAQuC,OAAQ,CACHxC,yBAAyBpB,KAAMqB,OACvC8D,cAYXvD,wBAA0B,CAACV,SAAUsC,SAEhC4B,WAAWC,oBAAoB,CAClCtB,QAAS,CACL,IACU7C,mBACOsC,WAGtB3B,MAAKyD,QAC2B,IAA3BA,OAAOC,SAASd,SAChBhF,YAAY6E,SAAQC,aAChBA,WAAWR,QAAQO,SAAQ,CAACkB,OAAQnE,SAC5BmE,OAAO9B,IAAMxC,WACbqD,WAAWR,QAAQ1C,OAAOoE,YAAcjC,eAI7C,KAIZR,MAAMF,aAAaG,WASpByC,gBAAkB1F,aACd2F,aAAe3F,KAAKE,KAAKC,mBAAUC,WAAWC,QAAQE,KAAK,qBAC3DqF,aAAe5F,KAAKE,KAAKC,mBAAUC,WAAWC,QAAQE,KAAK,4BAC1D0E,UAAUY,OAAOvG,oBAAqB,CACzCqG,aAAcA,aACdC,aAAcA,gBAWhBd,cAAgB,CAAC9E,KAAM8F,qBAEnBC,QAAUhG,gBAAgBC,UAE5BgG,gBAAkB,UAElBA,gBADoB,SAApBD,QAAQzF,QACUhB,wBACS,SAApByG,QAAQzF,QACGhB,uBAEAA,0BAGjBwG,cAI0C,IAAvCG,MAAMC,QAAQJ,YAAY/B,WAC1B+B,YAAY/B,QAAUoC,OAAOC,OAAON,YAAY/B,UAGpD+B,YAAY/B,QAAU+B,YAAY/B,QAAQsC,KAAIb,SAC1CA,OAAOc,mBAAmD,OAA9BP,QAAQrF,kBAC7B8E,UAEPM,YAAY/B,QAAQU,OACbQ,UAAUY,OAAOG,gBAAiB,CACrCjC,QAAS+B,YAAY/B,UAGlB2B,gBAAgB1F,OAhBpB0F,gBAAgB1F,OAuCzBuG,2BAA6B,CAACvG,KAAMH,mBAChC2G,MAAQ3G,UAAY4G,mBAAmBC,yBAC7C3E,OAAO4E,UAAUH,MAdJxG,CAAAA,MAEN4G,OAAS5G,KAAKE,KAAKC,mBAAUC,WAAWC,QAAQE,KAAK,cAAeqG,OAYnDC,CAAS7G,QAU/B8G,iBAAmB,CAACC,YAAa/G,YAC/BgH,aAAexH,mBAAmB6G,KAAIY,YAClCC,QAAS,SACTD,QAAUF,cACVG,QAAS,GAGN,CACHD,MAAOA,MACPC,OAAQA,iBAKVC,iBAAmBtD,SAAS7D,KAAKE,KAAKC,mBAAUC,WAAWC,QAAQE,KAAK,yBAA0B,WACjGyG,aAAaI,QAAOC,gBACI,IAAvBA,aAAaJ,OAAeE,iBAAmB,MAI5CE,aAAaJ,MAAQE,oBAa9BG,YAAc,SAACxB,YAAayB,YAAaC,SAAUC,aAASC,oEAAe,KAEzE3D,QAAU+B,YAAY/B,QAAU+B,YAAY/B,QAAU+B,YACtD6B,cAAgB,EAChBC,YAAc,WAGwB,IAA9BnI,YAAY8H,aAA+B,CACnDK,YAAcnI,YAAY8H,aAAaxD,cACjC8D,kBAAoBD,YAAYnD,OAClCoD,kBAAoBL,SAASZ,QAC7Be,cAAgBH,SAASZ,MAAQiB,kBACjCD,YAAc,IAAInI,YAAY8H,aAAaxD,WAAYA,QAAQM,MAAM,EAAGsD,sBAI5EA,cAAgBH,SAASZ,QAAS,EAClCgB,YAAeJ,SAASZ,MAAQ,EAAK7C,QAAQM,MAAM,EAAGmD,SAASZ,OAAS7C,QAI5EtE,YAAY8H,aAAe,CACvBxD,QAAS6D,mBAIPE,kBAAqC,IAAlBH,cAA0B5D,QAAQM,MAAMsD,cAAe5D,QAAQU,QAAU,GAC9FqD,iBAAiBrD,SACjBhF,YAAY8H,YAAc,GAAK,CAC3BxD,QAAS+D,mBAKbrI,YAAY8H,aAAaxD,QAAQU,OAAS+C,SAASZ,QAAUkB,iBAAiBrD,QAC9E9E,SAAW4H,YACU,OAAjBG,cACAD,QAAQM,eAAeR,mBAEsB,IAAlC9H,YAAY8H,YAAc,IACtC9H,YAAY8H,YAAc,GAAGxD,QAAQU,OAAS+C,SAASZ,QAC1DjH,SAAW4H,YAAc,GAG7B7H,aAAeoG,YAAYkC,YAMzBC,aAAe,KACjBvI,aAAe,EACfD,YAAc,GACdE,SAAW,EACXC,UAAY,GAQVsI,2BAA6B,KAC/BD,eACO,CAAClC,QAASwB,YAAaC,SAAUC,QAASzH,KAAMmI,SAAUvB,eACvDwB,YApiBO,EAACrC,QAASa,eACrByB,OAAS,CACXC,OAAQ5I,aACRkH,MAAOA,MACP2B,eAAgBxC,QAAQvF,SACxBC,KAAMsF,QAAQtF,KACdE,gBAAiBoF,QAAQpF,gBACzBC,iBAAkBmF,QAAQnF,wBAEN,YAApBmF,QAAQzF,SACR+H,OAAOG,eAAiBpD,WAAWqD,wBACnC3I,sBAAuB,GAEvBuI,OAAOG,eAAiBpD,WAAWsD,yBAEhCtD,WAAWuD,6BAA6BN,SAqhBvBO,CAChB7C,QACAa,OACF/E,MAAKiE,cACHwB,YAAYxB,YAAayB,YAAaC,SAAUC,SACzC3C,cAAc9E,KAAMP,YAAY8H,iBACxCvE,MAAMF,aAAaG,WAEtBkF,SAAShE,KAAKiE,eAShBS,yBAA2B,KAC7BZ,eACO,CAAClC,QAASwB,YAAaC,SAAUC,QAASzH,KAAMmI,SAAUvB,MAAOkC,oBAC9DC,iBA9hBa,EAAChD,QAASa,MAAOoC,qBAClCX,OAAS,CACXC,OAAQ5I,aACRkH,MAAOA,MACP2B,eAAgB,SAChB9H,KAAMsF,QAAQtF,KACdE,gBAAiBoF,QAAQpF,gBACzBC,iBAAkBmF,QAAQnF,iBAC1BqI,YAAaD,mBAEO,YAApBjD,QAAQzF,SACR+H,OAAOG,eAAiBpD,WAAWqD,wBACnC3I,sBAAuB,IAEvBuI,OAAOG,eAAiBpD,WAAWsD,yBACnC5I,sBAAuB,GAEpBsF,WAAWuD,6BAA6BN,SA6gBlBa,CACrBnD,QACAa,MACAkC,YACFjH,MAAKiE,cACHwB,YAAYxB,YAAayB,YAAaC,SAAUC,SACzC3C,cAAc9E,KAAMP,YAAY8H,iBACxCvE,MAAMF,aAAaG,WAEtBkF,SAAShE,KAAK4E,oBAWhBI,uBAAyB,SAACnJ,KAAMoJ,qBAAiBN,kEAAa,WAC1D/B,YAAclD,SAAS7D,KAAKE,KAAKC,mBAAUC,WAAWC,QAAQE,KAAK,eAAgB,QACrFyG,aAAeF,iBAAiBC,YAAa/G,YAE3CqJ,OAAS,IAAWxI,8BAC1BwI,OAAOC,eAAiBzJ,gBAElB0J,oBAAsB5E,oBAAoB6E,gBAC5CxC,cACA,CAACyC,UAAWhC,eACJU,SAAW,UACfsB,UAAUnF,SAAQkD,iBACRD,YAAcC,SAASkC,eACzB9C,MAASY,SAASZ,MAAQ,EAAKY,SAASZ,MAAQ,MAG/ChH,YAAegH,QAChBnH,YAAc,GACdC,aAAe,EACfC,SAAW,GAGXA,WAAa4H,mBAEbE,QAAQM,eAAepI,eACvBwI,SAAShE,KAAKW,cAAc9E,KAAMP,YAAY8H,eAIlD3H,UAAYgH,WAGkC,IAAlCnH,YAAY8H,YAAc,SACQ,IAA9B9H,YAAY8H,eACpBX,OAAS,SAKXb,QAAUhG,gBAAgBC,MAGhCoJ,gBAAgBrD,QAASwB,YAAaC,SAAUC,QAASzH,KAAMmI,SAAUvB,MAAOkC,eAE7EX,WAEXkB,QAGJE,oBAAoB1H,MAAK,CAACkD,KAAMC,MAC5BuB,2BAA2BvG,KAAMH,WAC1BoF,UAAUC,oBAAoBlF,KAAKE,KAAKC,mBAAUC,WAAWC,QAAS0E,KAAMC,OACpFhC,MAAMF,aAAaG,YASpB0G,uBAAyB,CAAC3J,KAAM4J,QAElCC,aAAaC,OAAO9J,KAAM,CACtB6J,aAAaE,OAAOC,WAGxBhK,KAAKiK,GAAGJ,aAAaE,OAAOC,SAAU7J,mBAAU+J,sBAAsB,CAACC,EAAGC,cAChEC,WAAY,mBAAEF,EAAEG,QAAQC,QAAQpK,mBAAU+J,sBAC1ChJ,SAAWI,YAAY+I,WAC7B5I,gBAAgBzB,KAAMkB,UACtBkJ,KAAKI,cAAcC,oBAGvBzK,KAAKiK,GAAGJ,aAAaE,OAAOC,SAAU7J,mBAAUuK,yBAAyB,CAACP,EAAGC,cACnEC,WAAY,mBAAEF,EAAEG,QAAQC,QAAQpK,mBAAUuK,yBAC1CxJ,SAAWI,YAAY+I,WAC7BnH,qBAAqBlD,KAAMkB,UAC3BkJ,KAAKI,cAAcC,oBAGvBzK,KAAKiK,GAAGJ,aAAaE,OAAOC,SAAU7J,mBAAUgB,gBAAgB,CAACgJ,EAAGC,QAChEA,KAAKI,cAAcC,oBAGvBzK,KAAKiK,GAAGJ,aAAaE,OAAOC,SAAU7J,mBAAUwK,oBAAoB,CAACR,EAAGC,cAC9DE,QAAS,mBAAEH,EAAEG,QAAQC,QAAQpK,mBAAUwK,oBACvCzJ,SAAWI,YAAYgJ,QAjdlB,EAACtK,KAAMkB,kBAChB0J,WAAavH,sBAAsBrD,KAAMkB,UACzC2J,WAAavH,sBAAsBtD,KAAMkB,UACzC6E,QAAUhG,gBAAgBC,MAEhCuD,qBAAqBrC,UAAU,GAI3B6E,QAAQvF,WAAajB,uCACrBkE,YAAYzD,KAAMkB,UAGtB0J,WAAWxI,SAAS,UACpByI,WAAW1I,YAAY,WAocnB2I,CAAW9K,KAAMkB,UACjBkJ,KAAKI,cAAcC,oBAGvBzK,KAAKiK,GAAGJ,aAAaE,OAAOC,SAAU7J,mBAAU4K,oBAAoB,CAACZ,EAAGC,cAC9DE,QAAS,mBAAEH,EAAEG,QAAQC,QAAQpK,mBAAU4K,oBACvC7J,SAAWI,YAAYgJ,QAjclB,EAACtK,KAAMkB,kBAChB0J,WAAavH,sBAAsBrD,KAAMkB,UACzC2J,WAAavH,sBAAsBtD,KAAMkB,UACzC6E,QAAUhG,gBAAgBC,MAEhCuD,qBAAqBrC,SAAU,MAI3B6E,QAAQvF,WAAajB,uCACrBkE,YAAYzD,KAAMkB,UAGtB0J,WAAWzI,YAAY,UACvB0I,WAAWzI,SAAS,WAobhB4I,CAAWhL,KAAMkB,UACjBkJ,KAAKI,cAAcC,0BAIjBQ,MAAQrB,KAAKsB,cAAc/K,mBAAUE,OAAO8K,aAC5CC,UAAYxB,KAAKsB,cAAc/K,mBAAUE,OAAO+K,WAEtDA,UAAUC,iBAAiB,SAAS,KAChCJ,MAAMhE,MAAQ,GACdgE,MAAMK,QACNC,YAAYH,UAAWpL,SAG3BiL,MAAMI,iBAAiB,SAAS,oBAAS,KACjB,KAAhBJ,MAAMhE,MACNsE,YAAYH,UAAWpL,OAEvB0H,aAAa0D,WACbjC,uBAAuBnJ,KAAM6I,2BAA4BoC,MAAMhE,MAAMuE,WAE1E,OASMD,YAAc,CAACH,UAAWpL,QACnCoL,UAAUK,UAAUC,IAAI,UACxBC,KAAK3L,8CAQH0H,aAAgB0D,YAClBA,UAAUK,UAAUtG,OAAO,WAQlBwG,KAAO3L,UAChBA,MAAO,mBAAEA,MACTP,YAAc,GACdE,SAAW,EACXD,aAAe,GAEVM,KAAKO,KAAK,aAAc,OACnBqJ,KAAOgC,SAASV,cAAc/K,mBAAUE,OAAOwL,aACrDlC,uBAAuB3J,KAAM4J,MAC7B/J,UAAY,oBAAsBG,KAAKO,KAAK,MAAQ,IAAMuL,KAAKC,SAC/D/L,KAAKO,KAAK,aAAa,GAG3B4I,uBAAuBnJ,KAAMkI,iEAYZlI,UACbP,YAAYgF,OAAS,EAAG,IAIA,YAHR1E,gBAAgBC,MAGpBM,SAA0BR,qBASlCL,YAAY6E,SAAQ,CAACC,WAAYlD,aACzBwD,iBAAmBzD,yBAAyBpB,KAAMqB,OACtDyD,cAAc9E,KAAMuE,YAAY1C,MAAK,CAACkD,KAAMC,KACjCC,UAAUC,oBAAoBL,iBAAkBE,KAAMC,MAC9DhC,MAAMF,aAAaG,kBAb8B,OAElDgI,MADOW,SAASV,cAAc/K,mBAAUE,OAAOwL,aAClCX,cAAc/K,mBAAUE,OAAO8K,aAC9B,KAAhBF,MAAMhE,MACNkC,uBAAuBnJ,KAAM6I,2BAA4BoC,MAAMhE,MAAMuE,QAErErC,uBAAuBnJ,KAAMkI,oCAWrCyD,KAAK3L"}PK'9\a[ǺAA myoverview/amd/build/view.min.jsnu[define("block_myoverview/view",["exports","jquery","block_myoverview/repository","core/paged_content_factory","core/pubsub","core/custom_interaction_events","core/notification","core/templates","core_course/events","block_myoverview/selectors","core/paged_content_events","core/aria","core/utils","core_user/repository"],(function(_exports,_jquery,Repository,PagedContentFactory,PubSub,CustomEvents,Notification,Templates,CourseEvents,_selectors,PagedContentEvents,Aria,_utils,_repository2){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Manage the courses view for the overview block. * * @copyright 2018 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.reset=_exports.init=_exports.clearSearch=void 0,_jquery=_interopRequireDefault(_jquery),Repository=_interopRequireWildcard(Repository),PagedContentFactory=_interopRequireWildcard(PagedContentFactory),PubSub=_interopRequireWildcard(PubSub),CustomEvents=_interopRequireWildcard(CustomEvents),Notification=_interopRequireWildcard(Notification),Templates=_interopRequireWildcard(Templates),CourseEvents=_interopRequireWildcard(CourseEvents),_selectors=_interopRequireDefault(_selectors),PagedContentEvents=_interopRequireWildcard(PagedContentEvents),Aria=_interopRequireWildcard(Aria);const TEMPLATES_COURSES_CARDS="block_myoverview/view-cards",TEMPLATES_COURSES_LIST="block_myoverview/view-list",TEMPLATES_COURSES_SUMMARY="block_myoverview/view-summary",TEMPLATES_NOCOURSES="core_course/no-courses",GROUPINGS_GROUPING_ALLINCLUDINGHIDDEN="allincludinghidden",NUMCOURSES_PERPAGE=[12,24,48,96,0];let loadedPages=[],courseOffset=0,lastPage=0,lastLimit=0,namespace=null,summaryDisplayLoaded=!1;const getFilterValues=root=>{const courseRegion=root.find(_selectors.default.courseView.region);return{display:courseRegion.attr("data-display"),grouping:courseRegion.attr("data-grouping"),sort:courseRegion.attr("data-sort"),displaycategories:courseRegion.attr("data-displaycategories"),customfieldname:courseRegion.attr("data-customfieldname"),customfieldvalue:courseRegion.attr("data-customfieldvalue")}},DEFAULT_PAGED_CONTENT_CONFIG={ignoreControlWhileLoading:!0,controlPlacementBottom:!0,persistentLimitKey:"block_myoverview_user_paging_preference"},getFavouriteIconContainer=(root,courseId)=>root.find(_selectors.default.FAVOURITE_ICON+'[data-course-id="'+courseId+'"]'),getPagedContentContainer=(root,index)=>root.find('[data-region="paged-content-page"][data-page="'+index+'"]'),getCourseId=root=>root.attr("data-course-id"),getAddFavouriteMenuItem=(root,courseId)=>root.find('[data-action="add-favourite"][data-course-id="'+courseId+'"]'),getRemoveFavouriteMenuItem=(root,courseId)=>root.find('[data-action="remove-favourite"][data-course-id="'+courseId+'"]'),addToFavourites=(root,courseId)=>{const removeAction=getRemoveFavouriteMenuItem(root,courseId),addAction=getAddFavouriteMenuItem(root,courseId);setCourseFavouriteState(courseId,!0).then((success=>{success?(PubSub.publish(CourseEvents.favourited,courseId),removeAction.removeClass("hidden"),addAction.addClass("hidden"),((root,courseId)=>{const iconContainer=getFavouriteIconContainer(root,courseId),isFavouriteIcon=iconContainer.find(_selectors.default.ICON_IS_FAVOURITE);isFavouriteIcon.removeClass("hidden"),Aria.unhide(isFavouriteIcon);const notFavourteIcon=iconContainer.find(_selectors.default.ICON_NOT_FAVOURITE);notFavourteIcon.addClass("hidden"),Aria.hide(notFavourteIcon)})(root,courseId)):Notification.alert("Starring course failed","Could not change favourite state")})).catch(Notification.exception)},removeFromFavourites=(root,courseId)=>{const removeAction=getRemoveFavouriteMenuItem(root,courseId),addAction=getAddFavouriteMenuItem(root,courseId);setCourseFavouriteState(courseId,!1).then((success=>{success?(PubSub.publish(CourseEvents.unfavorited,courseId),removeAction.addClass("hidden"),addAction.removeClass("hidden"),((root,courseId)=>{const iconContainer=getFavouriteIconContainer(root,courseId),isFavouriteIcon=iconContainer.find(_selectors.default.ICON_IS_FAVOURITE);isFavouriteIcon.addClass("hidden"),Aria.hide(isFavouriteIcon);const notFavourteIcon=iconContainer.find(_selectors.default.ICON_NOT_FAVOURITE);notFavourteIcon.removeClass("hidden"),Aria.unhide(notFavourteIcon)})(root,courseId)):Notification.alert("Starring course failed","Could not change favourite state")})).catch(Notification.exception)},getHideCourseMenuItem=(root,courseId)=>root.find('[data-action="hide-course"][data-course-id="'+courseId+'"]'),getShowCourseMenuItem=(root,courseId)=>root.find('[data-action="show-course"][data-course-id="'+courseId+'"]'),setCourseHiddenState=(courseId,status)=>(!1===status&&(status=null),(0,_repository2.setUserPreference)("block_myoverview_hidden_course_".concat(courseId),status).catch(Notification.exception)),hideElement=(root,id)=>{const pagingBar=root.find('[data-region="paging-bar"]'),jumpto=parseInt(pagingBar.attr("data-active-page-number"));let reducedCourse=loadedPages[jumpto].courses.reduce(((accumulator,current)=>(+id!=+current.id&&accumulator.push(current),accumulator)),[]);if(void 0!==loadedPages[jumpto+1]){const newElement=loadedPages[jumpto+1].courses.slice(0,1);loadedPages.forEach(((courseList,index)=>{if(index>jumpto){let popElement=[];void 0!==loadedPages[index+1]&&(popElement=loadedPages[index+1].courses.slice(0,1)),loadedPages[index].courses=[...loadedPages[index].courses.slice(1),...popElement]}})),reducedCourse=[...reducedCourse,...newElement]}if(lastPage===jumpto+1&&0===loadedPages[jumpto+1].courses.length){const pagedContentContainer=root.find('[data-region="paged-content-container"]');PagedContentFactory.resetLastPageNumber((0,_jquery.default)(pagedContentContainer).attr("id"),jumpto)}loadedPages[jumpto].courses=reducedCourse,courseOffset--;const pagedContentPage=getPagedContentContainer(root,jumpto);renderCourses(root,loadedPages[jumpto]).then(((html,js)=>Templates.replaceNodeContents(pagedContentPage,html,js))).catch(Notification.exception),loadedPages.forEach(((courseList,index)=>{if(index>jumpto){getPagedContentContainer(root,index).remove()}}))},setCourseFavouriteState=(courseId,status)=>Repository.setFavouriteCourses({courses:[{id:courseId,favourite:status}]}).then((result=>0===result.warnings.length&&(loadedPages.forEach((courseList=>{courseList.courses.forEach(((course,index)=>{course.id==courseId&&(courseList.courses[index].isfavourite=status)}))})),!0))).catch(Notification.exception),noCoursesRender=root=>{const nocoursesimg=root.find(_selectors.default.courseView.region).attr("data-nocoursesimg"),newcourseurl=root.find(_selectors.default.courseView.region).attr("data-newcourseurl");return Templates.render(TEMPLATES_NOCOURSES,{nocoursesimg:nocoursesimg,newcourseurl:newcourseurl})},renderCourses=(root,coursesData)=>{const filters=getFilterValues(root);let currentTemplate="";return currentTemplate="card"===filters.display?TEMPLATES_COURSES_CARDS:"list"===filters.display?TEMPLATES_COURSES_LIST:TEMPLATES_COURSES_SUMMARY,coursesData?(!1===Array.isArray(coursesData.courses)&&(coursesData.courses=Object.values(coursesData.courses)),coursesData.courses=coursesData.courses.map((course=>(course.showcoursecategory="on"===filters.displaycategories,course))),coursesData.courses.length?Templates.render(currentTemplate,{courses:coursesData.courses}):noCoursesRender(root)):noCoursesRender(root)},registerPagedEventHandlers=(root,namespace)=>{const event=namespace+PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT;PubSub.subscribe(event,(root=>limit=>root.find(_selectors.default.courseView.region).attr("data-paging",limit))(root))},itemsPerPageFunc=(pagingLimit,root)=>{let itemsPerPage=NUMCOURSES_PERPAGE.map((value=>{let active=!1;return value===pagingLimit&&(active=!0),{value:value,active:active}}));const totalCourseCount=parseInt(root.find(_selectors.default.courseView.region).attr("data-totalcoursecount"),10);return itemsPerPage.filter((pagingOption=>!(0===pagingOption.value&&totalCourseCount>100)&&pagingOption.value4&&void 0!==arguments[4]?arguments[4]:null,courses=coursesData.courses?coursesData.courses:coursesData,nextPageStart=0,pageCourses=[];if(void 0!==loadedPages[currentPage]){pageCourses=loadedPages[currentPage].courses;const currentPageLength=pageCourses.length;currentPageLength0?courses.slice(0,pageData.limit):courses;loadedPages[currentPage]={courses:pageCourses};const remainingCourses=!1!==nextPageStart?courses.slice(nextPageStart,courses.length):[];remainingCourses.length&&(loadedPages[currentPage+1]={courses:remainingCourses}),loadedPages[currentPage].courses.length{courseOffset=0,loadedPages=[],lastPage=0,lastLimit=0},standardFunctionalityCurry=()=>(resetGlobals(),(filters,currentPage,pageData,actions,root,promises,limit)=>{const pagePromise=((filters,limit)=>{const params={offset:courseOffset,limit:limit,classification:filters.grouping,sort:filters.sort,customfieldname:filters.customfieldname,customfieldvalue:filters.customfieldvalue};return"summary"===filters.display?(params.requiredfields=Repository.SUMMARY_REQUIRED_FIELDS,summaryDisplayLoaded=!0):params.requiredfields=Repository.CARDLIST_REQUIRED_FIELDS,Repository.getEnrolledCoursesByTimeline(params)})(filters,limit).then((coursesData=>(pageBuilder(coursesData,currentPage,pageData,actions),renderCourses(root,loadedPages[currentPage])))).catch(Notification.exception);promises.push(pagePromise)}),searchFunctionalityCurry=()=>(resetGlobals(),(filters,currentPage,pageData,actions,root,promises,limit,inputValue)=>{const searchingPromise=((filters,limit,searchValue)=>{const params={offset:courseOffset,limit:limit,classification:"search",sort:filters.sort,customfieldname:filters.customfieldname,customfieldvalue:filters.customfieldvalue,searchvalue:searchValue};return"summary"===filters.display?(params.requiredfields=Repository.SUMMARY_REQUIRED_FIELDS,summaryDisplayLoaded=!0):(params.requiredfields=Repository.CARDLIST_REQUIRED_FIELDS,summaryDisplayLoaded=!1),Repository.getEnrolledCoursesByTimeline(params)})(filters,limit,inputValue).then((coursesData=>(pageBuilder(coursesData,currentPage,pageData,actions),renderCourses(root,loadedPages[currentPage])))).catch(Notification.exception);promises.push(searchingPromise)}),initializePagedContent=function(root,promiseFunction){let inputValue=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const pagingLimit=parseInt(root.find(_selectors.default.courseView.region).attr("data-paging"),10);let itemsPerPage=itemsPerPageFunc(pagingLimit,root);const config={...DEFAULT_PAGED_CONTENT_CONFIG};config.eventNamespace=namespace;const pagedContentPromise=PagedContentFactory.createWithLimit(itemsPerPage,((pagesData,actions)=>{let promises=[];return pagesData.forEach((pageData=>{const currentPage=pageData.pageNumber;let limit=pageData.limit>0?pageData.limit:0;if(+lastLimit!=+limit&&(loadedPages=[],courseOffset=0,lastPage=0),lastPage===currentPage)return actions.allItemsLoaded(lastPage),void promises.push(renderCourses(root,loadedPages[currentPage]));lastLimit=limit,void 0===loadedPages[currentPage+1]&&void 0===loadedPages[currentPage]&&(limit*=2);const filters=getFilterValues(root);promiseFunction(filters,currentPage,pageData,actions,root,promises,limit,inputValue)})),promises}),config);pagedContentPromise.then(((html,js)=>(registerPagedEventHandlers(root,namespace),Templates.replaceNodeContents(root.find(_selectors.default.courseView.region),html,js)))).catch(Notification.exception)},registerEventListeners=(root,page)=>{CustomEvents.define(root,[CustomEvents.events.activate]),root.on(CustomEvents.events.activate,_selectors.default.ACTION_ADD_FAVOURITE,((e,data)=>{const favourite=(0,_jquery.default)(e.target).closest(_selectors.default.ACTION_ADD_FAVOURITE),courseId=getCourseId(favourite);addToFavourites(root,courseId),data.originalEvent.preventDefault()})),root.on(CustomEvents.events.activate,_selectors.default.ACTION_REMOVE_FAVOURITE,((e,data)=>{const favourite=(0,_jquery.default)(e.target).closest(_selectors.default.ACTION_REMOVE_FAVOURITE),courseId=getCourseId(favourite);removeFromFavourites(root,courseId),data.originalEvent.preventDefault()})),root.on(CustomEvents.events.activate,_selectors.default.FAVOURITE_ICON,((e,data)=>{data.originalEvent.preventDefault()})),root.on(CustomEvents.events.activate,_selectors.default.ACTION_HIDE_COURSE,((e,data)=>{const target=(0,_jquery.default)(e.target).closest(_selectors.default.ACTION_HIDE_COURSE),courseId=getCourseId(target);((root,courseId)=>{const hideAction=getHideCourseMenuItem(root,courseId),showAction=getShowCourseMenuItem(root,courseId),filters=getFilterValues(root);setCourseHiddenState(courseId,!0),filters.grouping!==GROUPINGS_GROUPING_ALLINCLUDINGHIDDEN&&hideElement(root,courseId),hideAction.addClass("hidden"),showAction.removeClass("hidden")})(root,courseId),data.originalEvent.preventDefault()})),root.on(CustomEvents.events.activate,_selectors.default.ACTION_SHOW_COURSE,((e,data)=>{const target=(0,_jquery.default)(e.target).closest(_selectors.default.ACTION_SHOW_COURSE),courseId=getCourseId(target);((root,courseId)=>{const hideAction=getHideCourseMenuItem(root,courseId),showAction=getShowCourseMenuItem(root,courseId),filters=getFilterValues(root);setCourseHiddenState(courseId,null),filters.grouping!==GROUPINGS_GROUPING_ALLINCLUDINGHIDDEN&&hideElement(root,courseId),hideAction.removeClass("hidden"),showAction.addClass("hidden")})(root,courseId),data.originalEvent.preventDefault()}));const input=page.querySelector(_selectors.default.region.searchInput),clearIcon=page.querySelector(_selectors.default.region.clearIcon);clearIcon.addEventListener("click",(()=>{input.value="",input.focus(),clearSearch(clearIcon,root)})),input.addEventListener("input",(0,_utils.debounce)((()=>{""===input.value?clearSearch(clearIcon,root):(activeSearch(clearIcon),initializePagedContent(root,searchFunctionalityCurry(),input.value.trim()))}),1e3))},clearSearch=(clearIcon,root)=>{clearIcon.classList.add("d-none"),init(root)};_exports.clearSearch=clearSearch;const activeSearch=clearIcon=>{clearIcon.classList.remove("d-none")},init=root=>{if(root=(0,_jquery.default)(root),loadedPages=[],lastPage=0,courseOffset=0,!root.attr("data-init")){const page=document.querySelector(_selectors.default.region.selectBlock);registerEventListeners(root,page),namespace="block_myoverview_"+root.attr("id")+"_"+Math.random(),root.attr("data-init",!0)}initializePagedContent(root,standardFunctionalityCurry())};_exports.init=init;_exports.reset=root=>{if(loadedPages.length>0){if("summary"!==getFilterValues(root).display||summaryDisplayLoaded)loadedPages.forEach(((courseList,index)=>{let pagedContentPage=getPagedContentContainer(root,index);renderCourses(root,courseList).then(((html,js)=>Templates.replaceNodeContents(pagedContentPage,html,js))).catch(Notification.exception)}));else{const input=document.querySelector(_selectors.default.region.selectBlock).querySelector(_selectors.default.region.searchInput);""!==input.value?initializePagedContent(root,searchFunctionalityCurry(),input.value.trim()):initializePagedContent(root,standardFunctionalityCurry())}}else init(root)}})); //# sourceMappingURL=view.min.js.mapPK'9\Y myoverview/amd/build/main.min.jsnu[define("block_myoverview/main",["exports","block_myoverview/view","block_myoverview/view_nav"],(function(_exports,View,ViewNav){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj} /** * Javascript to initialise the myoverview block. * * @copyright 2018 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,View=_interopRequireWildcard(View),ViewNav=_interopRequireWildcard(ViewNav);_exports.init=root=>{ViewNav.init(root),View.init(root)}})); //# sourceMappingURL=main.min.js.mapPK'9\$F\ù*myoverview/tests/privacy/provider_test.phpnu[. /** * Unit tests for the block_myoverview implementation of the privacy API. * * @package block_myoverview * @category test * @copyright 2018 Peter Dias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_myoverview\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\local\request\writer; use block_myoverview\privacy\provider; /** * Unit tests for the block_myoverview implementation of the privacy API. * * @copyright 2018 Peter Dias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends \core_privacy\tests\provider_testcase { /** * Ensure that export_user_preferences returns no data if the user has not visited the myoverview block. */ public function test_export_user_preferences_no_pref(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); provider::export_user_preferences($user->id); $writer = writer::with_context(\context_system::instance()); $this->assertFalse($writer->has_any_data()); } /** * Test the export_user_preferences given different inputs * * @param string $type The name of the user preference to get/set * @param string $value The value you are storing * * @dataProvider user_preference_provider */ public function test_export_user_preferences($type, $value, $expected): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); set_user_preference($type, $value, $user); provider::export_user_preferences($user->id); $writer = writer::with_context(\context_system::instance()); $blockpreferences = $writer->get_user_preferences('block_myoverview'); if (!$expected) { $expected = get_string($value, 'block_myoverview'); } $this->assertEquals($expected, $blockpreferences->{$type}->value); } /** * Create an array of valid user preferences for the myoverview block. * * @return array Array of valid user preferences. */ public static function user_preference_provider(): array { return array( array('block_myoverview_user_sort_preference', 'lastaccessed', ''), array('block_myoverview_user_sort_preference', 'title', ''), array('block_myoverview_user_sort_preference', 'shortname', ''), array('block_myoverview_user_grouping_preference', 'allincludinghidden', ''), array('block_myoverview_user_grouping_preference', 'all', ''), array('block_myoverview_user_grouping_preference', 'inprogress', ''), array('block_myoverview_user_grouping_preference', 'future', ''), array('block_myoverview_user_grouping_preference', 'past', ''), array('block_myoverview_user_grouping_preference', 'hidden', ''), array('block_myoverview_user_grouping_preference', 'favourites', ''), array('block_myoverview_user_view_preference', 'card', ''), array('block_myoverview_user_view_preference', 'list', ''), array('block_myoverview_user_view_preference', 'summary', ''), array('block_myoverview_user_paging_preference', 12, 12) ); } public function test_export_user_preferences_with_hidden_courses(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $name = "block_myoverview_hidden_course_1"; set_user_preference($name, 1, $user); provider::export_user_preferences($user->id); $writer = writer::with_context(\context_system::instance()); $blockpreferences = $writer->get_user_preferences('block_myoverview'); $this->assertEquals( get_string("privacy:request:preference:set", 'block_myoverview', (object) [ 'name' => $name, 'value' => 1, ]), $blockpreferences->{$name}->description ); } } PK'9\Xʼ$myoverview/tests/myoverview_test.phpnu[. namespace block_myoverview; /** * Online users testcase * * @package block_myoverview * @category test * @copyright 2019 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class myoverview_test extends \advanced_testcase { /** * Test getting block configuration */ public function test_get_block_config_for_external(): void { global $PAGE, $CFG, $OUTPUT; require_once($CFG->dirroot . '/my/lib.php'); $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']); $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 'categoryid' => $fieldcategory->get('id')]; $field = self::getDataGenerator()->create_custom_field($customfield); $customfieldvalue = ['shortname' => 'test', 'value' => 'Test value I']; $course1 = self::getDataGenerator()->create_course(['customfields' => [$customfieldvalue]]); $customfieldvalue = ['shortname' => 'test', 'value' => 'Test value II']; $course2 = self::getDataGenerator()->create_course(['customfields' => [$customfieldvalue]]); $this->getDataGenerator()->enrol_user($user->id, $course1->id, 'student'); $this->getDataGenerator()->enrol_user($user->id, $course2->id, 'student'); // Force a setting change to check the returned blocks settings. set_config('displaygroupingcustomfield', 1, 'block_myoverview'); set_config('customfiltergrouping', $field->get('shortname'), 'block_myoverview'); $this->setUser($user); $context = \context_user::instance($user->id); if (!$currentpage = my_get_page($user->id, MY_PAGE_PUBLIC, MY_PAGE_COURSES)) { throw new \moodle_exception('mymoodlesetup'); } $PAGE->set_url('/my/courses.php'); // Need this because some internal API calls require the $PAGE url to be set. $PAGE->set_context($context); $PAGE->set_pagelayout('mydashboard'); $PAGE->set_pagetype('my-index'); $PAGE->blocks->add_region('content'); // Need to add this special region to retrieve the central blocks. $PAGE->set_subpage($currentpage->id); // Load the block instances for all the regions. $PAGE->blocks->load_blocks(); $PAGE->blocks->create_all_block_instances(); $blocks = $PAGE->blocks->get_content_for_all_regions($OUTPUT); $configs = null; foreach ($blocks as $region => $regionblocks) { $regioninstances = $PAGE->blocks->get_blocks_for_region($region); foreach ($regioninstances as $ri) { // Look for myoverview block only. if ($ri->instance->blockname == 'myoverview') { $configs = $ri->get_config_for_external(); break 2; } } } // Test we receive all we expect (exact number and values of settings). $this->assertNotEmpty($configs); $this->assertEmpty((array) $configs->instance); $this->assertCount(13, (array) $configs->plugin); $this->assertEquals('test', $configs->plugin->customfiltergrouping); // Test default values. $this->assertEquals(1, $configs->plugin->displaycategories); $this->assertEquals(1, $configs->plugin->displaygroupingall); $this->assertEquals(0, $configs->plugin->displaygroupingallincludinghidden); $this->assertEquals(1, $configs->plugin->displaygroupingcustomfield); $this->assertEquals(1, $configs->plugin->displaygroupingfuture); $this->assertEquals(1, $configs->plugin->displaygroupinghidden); $this->assertEquals(1, $configs->plugin->displaygroupinginprogress); $this->assertEquals(1, $configs->plugin->displaygroupingpast); $this->assertEquals(1, $configs->plugin->displaygroupingfavourites); $this->assertEquals('card,list,summary', $configs->plugin->layouts); $this->assertEquals(get_config('block_myoverview', 'version'), $configs->plugin->version); // Test custom fields. $this->assertJson($configs->plugin->customfieldsexport); $fields = json_decode($configs->plugin->customfieldsexport); $this->assertEquals('Test value I', $fields[0]->name); $this->assertEquals('Test value I', $fields[0]->value); $this->assertFalse($fields[0]->active); $this->assertEquals('Test value II', $fields[1]->name); $this->assertEquals('Test value II', $fields[1]->value); $this->assertFalse($fields[1]->active); $this->assertEquals('No Custom field', $fields[2]->name); $this->assertFalse($fields[2]->active); } } PK'9\4MݔKK9myoverview/tests/behat/block_myoverview_dashboard.featurenu[@block @block_myoverview @javascript Feature: The my overview block allows users to easily access their courses In order to enable the my overview block in a course As a student I can add the my overview block to my dashboard Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | X | student1@example.com | S1 | And the following "categories" exist: | name | category | idnumber | | Category 1 | 0 | CAT1 | And the following "courses" exist: | fullname | shortname | category | startdate | enddate | | Course 1 | C1 | 0 | ##1 month ago## | ##15 days ago## | | Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## | | Course 3 | C3 | 0 | ##yesterday## | ##tomorrow## | | Course 4 | C4 | CAT1 | ##yesterday## | ##tomorrow## | | Course 5 | C5 | 0 | ##first day of next month## | ##last day of next month## | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | Scenario: View past courses Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "Past" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: View future courses Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "Future" "link" in the "Course overview" "block" Then I should see "Course 5" in the "Course overview" "block" And I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" Scenario: View inprogress courses Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "In progress" "link" in the "Course overview" "block" Then I should see "Course 2" in the "Course overview" "block" Then I should see "Course 3" in the "Course overview" "block" Then I should see "Course 4" in the "Course overview" "block" And I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: View all (except removed) courses Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "All" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" Then I should see "Course 2" in the "Course overview" "block" Then I should see "Course 3" in the "Course overview" "block" Then I should see "Course 4" in the "Course overview" "block" Then I should see "Course 5" in the "Course overview" "block" Scenario: View all (including removed from view) courses Given the following config values are set as admin: | config | value | plugin | | displaygroupingallincludinghidden | 1 | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" # We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (including removed from view)" element instead When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" Then I should see "Course 2" in the "Course overview" "block" Then I should see "Course 3" in the "Course overview" "block" Then I should see "Course 4" in the "Course overview" "block" Then I should see "Course 5" in the "Course overview" "block" Scenario: View inprogress courses - test persistence Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" And I click on "In progress" "link" in the "Course overview" "block" And I reload the page Then I should see "In progress" in the "Course overview" "block" Then I should see "Course 2" in the "Course overview" "block" Then I should see "Course 3" in the "Course overview" "block" Then I should see "Course 4" in the "Course overview" "block" And I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: View all (except removed) courses - w/ persistence Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "All" "link" in the "Course overview" "block" And I reload the page Then I should see "All" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" Then I should see "Course 2" in the "Course overview" "block" Then I should see "Course 3" in the "Course overview" "block" Then I should see "Course 4" in the "Course overview" "block" Then I should see "Course 5" in the "Course overview" "block" Scenario: View past courses - w/ persistence Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "Past" "link" in the "Course overview" "block" And I reload the page Then I should see "Past" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: View future courses - w/ persistence Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "Future" "link" in the "Course overview" "block" And I reload the page Then I should see "Future" in the "Course overview" "block" Then I should see "Course 5" in the "Course overview" "block" And I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" Scenario: View favourite courses - w/ persistence Given I am on the "My courses" page logged in as "student1" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Star this course" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "All" "button" in the "Course overview" "block" When I click on "Starred" "link" in the "Course overview" "block" And I reload the page Then I should see "Starred" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" And I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: List display persistence Given I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" And I click on "List" "link" in the "Course overview" "block" And I reload the page Then I should see "List" in the "Course overview" "block" And "[data-display='list']" "css_element" in the "Course overview" "block" should be visible Scenario: Cards display persistence Given I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" And I click on "Card" "link" in the "Course overview" "block" And I reload the page Then I should see "Card" in the "Course overview" "block" And "[data-display='card']" "css_element" in the "Course overview" "block" should be visible Scenario: Summary display persistence Given I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" And I click on "Summary" "link" in the "Course overview" "block" And I reload the page Then I should see "Summary" in the "Course overview" "block" And "[data-display='summary']" "css_element" in the "Course overview" "block" should be visible Scenario: Course name sort persistence Given I am on the "My courses" page logged in as "student1" And I click on "sortingdropdown" "button" in the "Course overview" "block" And I click on "Sort by course name" "link" in the "Course overview" "block" And I reload the page Then I should see "Sort by course name" in the "Course overview" "block" And "[data-sort='fullname']" "css_element" in the "Course overview" "block" should be visible Scenario: Last accessed sort persistence Given I am on the "My courses" page logged in as "student1" And I click on "sortingdropdown" "button" in the "Course overview" "block" And I click on "Sort by last accessed" "link" in the "Course overview" "block" And I reload the page Then I should see "Sort by last accessed" in the "Course overview" "block" And "[data-sort='ul.timeaccess desc']" "css_element" in the "Course overview" "block" should be visible Scenario: Short name sort persistence Given I am on the "My courses" page logged in as "student1" When I click on "sortingdropdown" "button" in the "Course overview" "block" Then I should not see "Sort by short name" in the "Course overview" "block" When the following config values are set as admin: | config | value | | courselistshortnames | 1 | And I reload the page And I click on "sortingdropdown" "button" in the "Course overview" "block" And I click on "Sort by short name" "link" in the "Course overview" "block" And I reload the page Then I should see "Sort by short name" in the "Course overview" "block" And "[data-sort='shortname']" "css_element" in the "Course overview" "block" should be visible Scenario: View inprogress courses with hide persistent functionality Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "In progress" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I reload the page Then I should see "Course 3" in the "Course overview" "block" Then I should see "Course 4" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: View past courses with hide persistent functionality Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "Past" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 1')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 1')]" "xpath_element" And I reload the page Then I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: View future courses with hide persistent functionality Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "Future" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 5')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 5')]" "xpath_element" And I reload the page Then I should not see "Course 5" in the "Course overview" "block" And I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" Scenario: View all (except hidden) courses with hide persistent functionality Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "All" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 5')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 5')]" "xpath_element" And I reload the page Then I should not see "Course 5" in the "Course overview" "block" And I should see "Course 1" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" And I should see "Course 3" in the "Course overview" "block" And I should see "Course 4" in the "Course overview" "block" Scenario: View all (including removed from view) courses with hide persistent functionality Given the following config values are set as admin: | config | value | plugin | | displaygroupingallincludinghidden | 1 | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" # We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (including removed from view)" element instead When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 5')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 5')]" "xpath_element" And I reload the page Then I should see "Course 5" in the "Course overview" "block" And I should see "Course 1" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" And I should see "Course 3" in the "Course overview" "block" And I should see "Course 4" in the "Course overview" "block" Scenario: Show course category in cards display Given the following config values are set as admin: | displaycategories | 1 | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "Card" "link" in the "Course overview" "block" Then I should see "Category 1" in the "Course overview" "block" Scenario: Show course category in list display Given the following config values are set as admin: | displaycategories | 1 | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "List" "link" in the "Course overview" "block" Then I should see "Category 1" in the "Course overview" "block" Scenario: Show course category in summary display Given the following config values are set as admin: | displaycategories | 1 | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "Summary" "link" in the "Course overview" "block" Then I should see "Category 1" in the "Course overview" "block" Scenario: Hide course category in cards display Given the following config values are set as admin: | displaycategories | 0 | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "Card" "link" in the "Course overview" "block" Then I should not see "Category 1" in the "Course overview" "block" Scenario: Hide course category in list display Given the following config values are set as admin: | displaycategories | 0 | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "List" "link" in the "Course overview" "block" Then I should not see "Category 1" in the "Course overview" "block" Scenario: Show course category in summary display Given the following config values are set as admin: | displaycategories | 0 | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "Summary" "link" in the "Course overview" "block" Then I should not see "Category 1" in the "Course overview" "block" @accessibility Scenario: The My courses page must meet accessibility standards When I am on the "My courses" page logged in as "student1" Then the page should meet accessibility standards with "best-practice" extra tests PK'9\2C Emyoverview/tests/behat/block_myoverview_pagelimit_persistence.featurenu[@block @block_myoverview @javascript Feature: The my overview block allows users to persistence of their page limits Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | X | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C01 | 0 | | Course 2 | C02 | 0 | | Course 3 | C03 | 0 | | Course 4 | C04 | 0 | | Course 5 | C05 | 0 | | Course 6 | C06 | 0 | | Course 7 | C07 | 0 | | Course 8 | C08 | 0 | | Course 9 | C09 | 0 | | Course 10 | C10 | 0 | | Course 11 | C11 | 0 | | Course 12 | C12 | 0 | | Course 13 | C13 | 0 | And the following "course enrolments" exist: | user | course | role | | student1 | C01 | student | | student1 | C02 | student | | student1 | C03 | student | | student1 | C04 | student | | student1 | C05 | student | | student1 | C06 | student | | student1 | C07 | student | | student1 | C08 | student | | student1 | C09 | student | | student1 | C10 | student | | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | Scenario: Toggle the page limit between page reloads Given I am on the "My courses" page logged in as "student1" When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block" And I click on "All" "link" in the ".dropdown-menu.show" "css_element" Then I should see "Course 13" And I reload the page Then I should see "Course 13" And I should see "All" in the ".block-myoverview [data-action='limit-toggle']" "css_element" Scenario: Toggle the page limit between grouping changes Given I am on the "My courses" page logged in as "student1" When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block" And I click on "All" "link" in the ".dropdown-menu.show" "css_element" And I click on "All" "button" in the "Course overview" "block" And I click on "In progress" "link" in the "Course overview" "block" Then I should see "Course 13" And I should see "All" in the ".block-myoverview [data-action='limit-toggle']" "css_element" PK'9\|C C 6myoverview/tests/behat/block_myoverview_search.featurenu[@block @block_myoverview @javascript Feature: My overview block searching Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | X | student1@example.com | S1 | | student2 | Student | Y | student2@example.com | S2 | And the following "courses" exist: | fullname | shortname | category | | Course 01 | C1 | 0 | | Course 02 | C2 | 0 | | Course 03 | C3 | 0 | | Course 04 | C4 | 0 | | Course 05 | C5 | 0 | | Course 06 | C6 | 0 | | Course 07 | C7 | 0 | | Course 08 | C8 | 0 | | Course 09 | C9 | 0 | | Course 10 | C10 | 0 | | Course 11 | C11 | 0 | | Course 12 | C12 | 0 | | Course 13 | C13 | 0 | | Fake example | Fake | 0 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | | student1 | C6 | student | | student1 | C7 | student | | student1 | C8 | student | | student1 | C9 | student | | student1 | C10 | student | | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | Scenario: There is no search if I am not enrolled in any course When I am on the "My courses" page logged in as "student2" Then I should see "You're not enrolled in any courses." in the "Course overview" "block" And "Search courses" "field" should not exist in the "Course overview" "block" And I log out Scenario: Single page search Given I am on the "My courses" page logged in as "student1" And I set the field "Search courses" in the "Course overview" "block" to "Course 0" Then I should see "Course 01" in the "Course overview" "block" And I should not see "Course 13" in the "Course overview" "block" And I log out Scenario: Paginated search Given I am on the "My courses" page logged in as "student1" And I set the field "Search courses" in the "Course overview" "block" to "Course" And I should see "Course 01" in the "Course overview" "block" And I should not see "Course 13" in the "Course overview" "block" And I click on "[data-control='next']" "css_element" in the "Course overview" "block" And I wait until ".block_myoverview [data-control='next']" "css_element" exists Then I should see "Course 13" in the "Course overview" "block" And I should not see "Course 01" in the "Course overview" "block" PK'9\{!!6myoverview/tests/behat/block_myoverview_hidden.featurenu[@block @block_myoverview @javascript Feature: The my overview block allows users to hide their courses In order to enable the my overview block in a course As a student I can add the my overview block to my dashboard Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | X | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | | Course 2 | C2 | 0 | | Course 3 | C3 | 0 | | Course 4 | C4 | 0 | | Course 5 | C5 | 0 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | Scenario: Test hide toggle functionality Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "All" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I reload the page Then I should not see "Course 2" in the "Course overview" "block" Scenario: Test hide toggle functionality w/ favorites Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" And I click on "All" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Star this course" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" When I reload the page And I should not see "Course 2" in the "Course overview" "block" And I click on "All" "button" in the "Course overview" "block" And I click on "Starred" "link" in the "Course overview" "block" Then I should not see "Course 2" in the "Course overview" "block" And I click on "Starred" "button" in the "Course overview" "block" And I click on "Removed from view" "link" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" Scenario: Test show toggle functionality Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" And I click on "All" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "All" "button" in the "Course overview" "block" And I click on "Removed from view" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Restore to view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I reload the page And I should not see "Course 2" in the "Course overview" "block" And I click on "Removed from view" "button" in the "Course overview" "block" When I click on "All" "link" in the "Course overview" "block" And I reload the page Then I should see "Course 2" in the "Course overview" "block" Scenario: Test star and unstar functionality Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" And I click on "All" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Star this course" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "All" "button" in the "Course overview" "block" And I click on "Removed from view" "link" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Restore to view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" When I reload the page Then I should not see "Course 2" in the "Course overview" "block" And I click on "Removed from view" "button" in the "Course overview" "block" And I click on "All" "link" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" And I click on "All" "button" in the "Course overview" "block" And I click on "Starred" "link" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" Scenario: Test a course is hidden directly with "All" courses Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "All" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" Then I should not see "Course 2" in the "Course overview" "block" Scenario: Test a course is never hidden with "All (including removed from view)" courses Given the following config values are set as admin: | config | value | plugin | | displaygroupingallincludinghidden | 1 | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" # We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (except hidden)" element instead When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Remove from view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" Then I should see "Course 2" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I should not see "Remove from view" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I should see "Restore to view" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Restore to view" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I should see "Course 2" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I should see "Remove from view" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I should not see "Restore to view" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" PK'9\!RqC+C+=myoverview/tests/behat/block_myoverview_adminsettings.featurenu[@block @block_myoverview @javascript Feature: The my overview block allows admins to easily configure the students' course list In order to adapt the my overview block to my users' needs As an admin I can configure the appearance of the my overview block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | X | student1@example.com | S1 | And the following "categories" exist: | name | category | idnumber | | Category 1 | 0 | CAT1 | And the following "courses" exist: | fullname | shortname | category | startdate | enddate | | Course 1 | C1 | 0 | ##1 month ago## | ##15 days ago## | | Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## | | Course 3 | C3 | 0 | ##yesterday## | ##tomorrow## | | Course 4 | C4 | CAT1 | ##yesterday## | ##tomorrow## | | Course 5 | C5 | 0 | ##first day of next month## | ##last day of next month## | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | Scenario: Enable 'All (including removed from view)' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "All (including removed from view)" to "1" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" # We have to check for the data attribute instead of the list element text as we would get false positives from the "All" element otherwise Then "All (including removed from view)" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Disable 'All (including removed from view)' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "All (including removed from view)" to "0" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "All (including removed from view)" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Enable 'All' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "All" in the "//*[@id=\"admin-displaygroupingall\"]" "xpath_element" to "1" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "[data-value='all']" "css_element" should exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Disable 'All' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "All" in the "//*[@id=\"admin-displaygroupingall\"]" "xpath_element" to "0" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" # 'All' option has been disabled, so the button is falling back to the 'In progress' option which is the next enabled option. And I click on "In progress" "button" in the "Course overview" "block" Then "[data-value='all']" "css_element" should not exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Enable 'In progress' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "In progress" to "1" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" # We have to check for the data attribute instead of the list element text as we would get false negatives "All (including removed from view)" element otherwise Then "In progress" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Disable 'In progress' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "In progress" to "0" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "In progress" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Enable 'Future' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "Future" to "1" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "Future" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Disable 'Future' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "Future" to "0" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "Future" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Enable 'Past' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "Past" to "1" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "Past" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Disable 'Past' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "Past" to "0" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "Past" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Enable 'Starred' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "Starred" to "1" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "Starred" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Disable 'Starred' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "Starred" to "0" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "Starred" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Enable 'Removed courses' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "Removed from view" to "1" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "Removed from view" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Disable 'Removed courses' course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "Removed from view" to "0" And I press "Save" And I log out Then I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then "Removed from view" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" Scenario: Disable all course filter options Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "All (including removed from view)" to "0" And I set the field "All" in the "//*[@id=\"admin-displaygroupingall\"]" "xpath_element" to "0" And I set the field "In progress" to "0" And I set the field "Future" to "0" And I set the field "Past" to "0" And I set the field "Starred" to "0" And I set the field "Removed from view" to "0" And I press "Save" And I log out And I am on the "My courses" page logged in as "student1" Then "button#groupingdropdown" "css_element" should not exist in the ".block_myoverview" "css_element" And I should see "Course 1" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" And I should see "Course 3" in the "Course overview" "block" And I should see "Course 4" in the "Course overview" "block" And I should see "Course 5" in the "Course overview" "block" Scenario: Disable all but one course filter option Given I log in as "admin" And I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "All (including removed from view)" to "0" And I set the field "All" in the "//*[@id=\"admin-displaygroupingall\"]" "xpath_element" to "0" And I set the field "In progress" to "1" And I set the field "Future" to "0" And I set the field "Past" to "0" And I set the field "Starred" to "0" And I set the field "Removed from view" to "0" And I press "Save" And I log out And I am on the "My courses" page logged in as "student1" Then "button#groupingdropdown" "css_element" should not exist in the ".block_myoverview" "css_element" And I should see "Course 2" in the "Course overview" "block" And I should see "Course 3" in the "Course overview" "block" And I should see "Course 4" in the "Course overview" "block" And I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" PK'9\J*q11;myoverview/tests/behat/block_myoverview_customfield.featurenu[@block @block_myoverview @javascript Feature: The my overview block allows users to group courses by custom fields Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | X | student1@example.com | S1 | And the following "custom field categories" exist: | name | component | area | itemid | | Course fields | core_course | course | 0 | And the following "custom fields" exist: | name | category | type | shortname | configdata | | Checkbox field | Course fields | checkbox | checkboxfield | | | Date field | Course fields | date | datefield | {"mindate":0, "maxdate":0} | | Select field | Course fields | select | selectfield | {"options":"Option 1\nOption 2\nOption 3\nOption 4"} | | Text field | Course fields | text | textfield | {"visibility":"2"} | | Text field 2 | Course fields | text | textfield2 | {"visibility":"2"} | | Hidden field | Course fields | text | hiddenfield | {"visibility":"0"} | And the following "courses" exist: | fullname | shortname | category | customfield_checkboxfield | customfield_datefield | customfield_selectfield | customfield_textfield | customfield_textfield2 | | Course 1 | C1 | 0 | 1 | 981028800 | 1 | fish | penguin | | Course 2 | C2 | 0 | 0 | 334324800 | | | | | Course 3 | C3 | 0 | 0 | 981028800 | 2 | dog | | | Course 4 | C4 | 0 | 1 | | 3 | cat | | | Course 5 | C5 | 0 | | 334411200 | 2 | fish | penguin | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | Scenario: Group courses by checkbox: Yes Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | checkboxfield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "Checkbox field: Yes" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: Group courses by checkbox: No Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | checkboxfield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "Checkbox field: No" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" And I should see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should see "Course 5" in the "Course overview" "block" Scenario: Group courses by date: 1 February 2001 Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | datefield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "1 February 2001" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: Group courses by date: 6 August 1980 Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | datefield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "6 August 1980" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should see "Course 5" in the "Course overview" "block" Scenario: Group courses by date: No Date field Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | datefield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "No Date field" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: Group courses by select: Option 1 Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | selectfield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" And I should not see "Option 4" in the "Course overview" "block" When I click on "Option 1" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: Group courses by select: Option 2 Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | selectfield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "Option 2" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should see "Course 5" in the "Course overview" "block" Scenario: Group courses by select: No Select field Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | selectfield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "No Select field" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: Group courses by text: fish Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | textfield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "fish" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should see "Course 5" in the "Course overview" "block" Scenario: Group courses by text: dog Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | textfield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "dog" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" And I should not see "Course 2" in the "Course overview" "block" And I should see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: Group courses by text: No Text field Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | textfield | block_myoverview | And I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "No Text field" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" And I should not see "Course 3" in the "Course overview" "block" And I should not see "Course 4" in the "Course overview" "block" And I should not see "Course 5" in the "Course overview" "block" Scenario: Hidden fields not displayed when configuring the custom field filters Given I log in as "admin" When I navigate to "Plugins > Blocks > Course overview" in site administration And I set the field "Custom field" to "1" Then the "Field to use" select box should not contain "Hidden field" @core_customfield Scenario: Hidden fields not displayed in the filter Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | textfield2 | block_myoverview | And I log in as "admin" And I navigate to "Courses > Default settings > Course custom fields" in site administration And I press "Edit custom field: Text field 2" And I set the field "Visible to" to "Nobody" And I press "Save changes" And I log out When I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then I should not see "penguin" in the "Course overview" "block" Then I should not see "No text field" in the "Course overview" "block" PK'9\l9myoverview/tests/behat/block_myoverview_zerostate.featurenu[@block @block_myoverview Feature: Zero state on my overview block In order to know what should be the next step As a user I should see the proper information based on my capabilities Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | user | User | X | user@example.com | U1 | | manager | Manager | X | manager@example.com | M1 | And the following "role assigns" exist: | user | role | contextlevel | reference | | manager | manager | System | | Scenario: Users with no permissions don't see any CTA Given I am on the "My courses" page logged in as "user" When I should see "You're not enrolled in any courses." Then I should see "Once you're enrolled in a course, it will appear here." And I should not see "Create course" And I should not see "Request a course" Scenario: Users with permissions to request a course should see a Request course button Given the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/course:request | Allow | user | System | | When I am on the "My courses" page logged in as "user" Then I should see "Request your first course" And "Moodle documentation" "link" should exist And "Quickstart guide" "link" should exist And "Request a course" "button" should exist And I click on "Request a course" "button" And I should see "Details of the course" # Quickstart guide link should not be displayed when $CFG->coursecreationguide is empty. But the following config values are set as admin: | coursecreationguide | | And I am on the "My courses" page And "Moodle documentation" "link" should exist And "Quickstart guide" "link" should not exist Scenario: Users with permissions to create a course when there is no course created Given I am on the "My courses" page logged in as "manager" When I should see "Create your first course" Then "Moodle documentation" "link" should exist And "Quickstart guide" "link" should exist And "Manage courses" "button" should not exist And "Manage course categories" "button" should exist And "Create course" "button" should exist And I click on "Create course" "button" And I should see "Add a new course" # Quickstart guide link should not be displayed when $CFG->coursecreationguide is empty. But the following config values are set as admin: | coursecreationguide | | And I am on the "My courses" page And "Moodle documentation" "link" should exist And "Quickstart guide" "link" should not exist Scenario: Users with permissions to create a course but is not enrolled in any existing course Given the following "course" exists: | fullname | Course 1 | | shortname | C1 | When I am on the "My courses" page logged in as "manager" Then I should see "You're not enrolled in any courses." Then I should see "Once you're enrolled in a course, it will appear here." And "Manage courses" "button" should exist And "Create course" "button" should exist And I click on "Create course" "button" And I should see "Add a new course" And I am on the "My courses" page And I click on "Manage courses" "button" And I should see "Course 1" Scenario: Users with permissions to create but not to manage courses and is not enrolled in any existing course Given the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/category:manage | Prohibit | manager | System | | And the following "course" exists: | fullname | Course 1 | | shortname | C1 | When I am on the "My courses" page logged in as "manager" Then I should see "You're not enrolled in any courses." Then I should not see "To view all courses on this sie, go to Manage courses" And "Manage courses" "button" should not exist And "Create course" "button" should exist And I click on "Create course" "button" And I should see "Add a new course" @javascript @accessibility Scenario: Evaluate the accessibility of the My courses (zero state) When I am on the "My courses" page logged in as "manager" Then the page should meet accessibility standards with "best-practice" extra tests PK'9\0Љ!!:myoverview/tests/behat/block_myoverview_pagination.featurenu[@block @block_myoverview @javascript Feature: My overview block pagination Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | X | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | | Course 01 | C1 | 0 | | Course 02 | C2 | 0 | | Course 03 | C3 | 0 | | Course 04 | C4 | 0 | | Course 05 | C5 | 0 | | Course 06 | C6 | 0 | | Course 07 | C7 | 0 | | Course 08 | C8 | 0 | | Course 09 | C9 | 0 | | Course 10 | C10 | 0 | | Course 11 | C11 | 0 | | Course 12 | C12 | 0 | | Course 13 | C13 | 0 | | Course 14 | C14 | 0 | | Course 15 | C15 | 0 | | Course 16 | C16 | 0 | | Course 17 | C17 | 0 | | Course 18 | C18 | 0 | | Course 19 | C19 | 0 | | Course 20 | C20 | 0 | | Course 21 | C21 | 0 | | Course 22 | C22 | 0 | | Course 23 | C23 | 0 | | Course 24 | C24 | 0 | | Course 25 | C25 | 0 | Scenario: The pagination controls should be hidden if I am not enrolled in any courses When I am on the "My courses" page logged in as "student1" Then I should see "You're not enrolled in any courses." in the "Course overview" "block" And I should not see "Show" in the "Course overview" "block" And ".block_myoverview .dropdown-menu.show" "css_element" should not be visible And ".block_myoverview [data-control='next']" "css_element" should not be visible And ".block_myoverview [data-control='previous']" "css_element" should not be visible And I log out Scenario: The pagination controls should be hidden if I am enrolled in 12 courses or less Given the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | | student1 | C6 | student | | student1 | C7 | student | | student1 | C8 | student | | student1 | C9 | student | | student1 | C10 | student | | student1 | C11 | student | | student1 | C12 | student | When I am on the "My courses" page logged in as "student1" Then I should not see "Show" in the "Course overview" "block" And ".block_myoverview .dropdown-menu.show" "css_element" should not be visible And ".block_myoverview [data-control='next']" "css_element" should not be visible And ".block_myoverview [data-control='previous']" "css_element" should not be visible And I log out Scenario: The default pagination should be 12 courses Given the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | | student1 | C6 | student | | student1 | C7 | student | | student1 | C8 | student | | student1 | C9 | student | | student1 | C10 | student | | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | When I am on the "My courses" page logged in as "student1" Then I should see "12" in the ".block_myoverview [data-action='limit-toggle']" "css_element" And I log out Scenario: I should only see pagination limit options less than total number of enrolled courses Given the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | | student1 | C6 | student | | student1 | C7 | student | | student1 | C8 | student | | student1 | C9 | student | | student1 | C10 | student | | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | And I am on the "My courses" page logged in as "student1" When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block" Then I should see "All" in the ".dropdown-menu.show" "css_element" And I should see "12" in the ".dropdown-menu.show" "css_element" And ".block_myoverview [data-control='next']" "css_element" should be visible And ".block_myoverview [data-control='previous']" "css_element" should be visible But I should not see "24" in the ".block_myoverview .dropdown-menu.show" "css_element" And I log out Scenario: Previous page button should be disabled when on the first page of courses Given the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | | student1 | C6 | student | | student1 | C7 | student | | student1 | C8 | student | | student1 | C9 | student | | student1 | C10 | student | | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | When I am on the "My courses" page logged in as "student1" Then the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should contain "disabled" And I log out Scenario: Next page button should be disabled when on the last page of courses Given the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | | student1 | C6 | student | | student1 | C7 | student | | student1 | C8 | student | | student1 | C9 | student | | student1 | C10 | student | | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | When I am on the "My courses" page logged in as "student1" And I wait until ".block_myoverview [data-control='next']" "css_element" exists And I click on "[data-control='next']" "css_element" in the "Course overview" "block" Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should contain "disabled" And I log out Scenario: Next and previous page buttons should both be enabled when not on last or first page of courses Given the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | | student1 | C6 | student | | student1 | C7 | student | | student1 | C8 | student | | student1 | C9 | student | | student1 | C10 | student | | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | | student1 | C14 | student | | student1 | C15 | student | | student1 | C16 | student | | student1 | C17 | student | | student1 | C18 | student | | student1 | C19 | student | | student1 | C20 | student | | student1 | C21 | student | | student1 | C22 | student | | student1 | C23 | student | | student1 | C24 | student | | student1 | C25 | student | When I am on the "My courses" page logged in as "student1" And I wait until ".block_myoverview [data-control='next']" "css_element" exists And I click on "[data-control='next']" "css_element" in the "Course overview" "block" Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should not contain "disabled" And the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should not contain "disabled" And I should see "Course 13" in the "Course overview" "block" And I should see "Course 24" in the "Course overview" "block" But I should not see "Course 12" in the "Course overview" "block" And I should not see "Course 25" in the "Course overview" "block" And I log out PK'9\9myoverview/tests/behat/block_myoverview_favourite.featurenu[@block @block_myoverview @javascript Feature: The my overview block allows users to favourite their courses In order to enable the my overview block in a course As a student I can add the my overview block to my dashboard Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | X | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | | Course 2 | C2 | 0 | | Course 3 | C3 | 0 | | Course 4 | C4 | 0 | | Course 5 | C5 | 0 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | | student1 | C3 | student | | student1 | C4 | student | | student1 | C5 | student | Scenario: Favourite a course on a course card Given I am on the "My courses" page logged in as "student1" When I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I click on "Star this course" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 2')]" "xpath_element" And I reload the page Then "//div[contains(@class, 'course-card') and contains(.,'Course 2')]//span[@data-region='is-favourite' and @aria-hidden='false']" "xpath_element" should exist And "//div[contains(@class, 'course-card') and contains(.,'Course 2')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should not exist And "//div[contains(@class, 'course-card') and contains(.,'Course 1')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist And "//div[contains(@class, 'course-card') and contains(.,'Course 3')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist Scenario: Star a course and switch display to list Given I am on the "My courses" page logged in as "student1" When I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 5')]" "xpath_element" And I click on "Star this course" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 5')]" "xpath_element" And I click on "Display drop-down menu" "button" in the "Course overview" "block" And I click on "List" "link" in the "Course overview" "block" Then "//li[contains(concat(' ', normalize-space(@class), ' '), 'list-group-item') and contains(.,'Course 5')]//span[@data-region='is-favourite' and @aria-hidden='false']" "xpath_element" should exist And "//li[contains(concat(' ', normalize-space(@class), ' '), 'list-group-item') and contains(.,'Course 5')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should not exist And "//li[contains(concat(' ', normalize-space(@class), ' '), 'list-group-item') and contains(.,'Course 1')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist And "//li[contains(concat(' ', normalize-space(@class), ' '), 'list-group-item') and contains(.,'Course 3')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist Scenario: Star a course and switch display to summary Given I am on the "My courses" page logged in as "student1" When I click on ".coursemenubtn" "css_element" in the "//div[contains(@class, 'course-card') and contains(.,'Course 5')]" "xpath_element" And I click on "Star this course" "link" in the "//div[contains(@class, 'course-card') and contains(.,'Course 5')]" "xpath_element" And I click on "Display drop-down menu" "button" in the "Course overview" "block" And I click on "Summary" "link" in the "Course overview" "block" Then "//div[contains(concat(' ', normalize-space(@class), ' '), 'course-summaryitem') and contains(.,'Course 5')]//span[@data-region='is-favourite' and @aria-hidden='false']" "xpath_element" should exist And "//div[contains(concat(' ', normalize-space(@class), ' '), 'course-summaryitem') and contains(.,'Course 5')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should not exist And "//div[contains(concat(' ', normalize-space(@class), ' '), 'course-summaryitem') and contains(.,'Course 1')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist And "//div[contains(concat(' ', normalize-space(@class), ' '), 'course-summaryitem') and contains(.,'Course 3')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist PK'9\lOR 8myoverview/tests/behat/block_myoverview_progress.featurenu[@block @block_myoverview @javascript Feature: Course overview block show users their progress on courses In order to enable the my overview block in a course As a student I can see the progress percentage of the courses I am enrolled in Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | enablecompletion | startdate | enddate | | Course 1 | C1 | 0 | 1 | ##yesterday## | ##tomorrow## | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | timeclose | | choice | C1 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | Scenario: Course progress percentage should not be displayed if completion is not enabled Given I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" When I click on "All" "link" in the "Course overview" "block" Then I should not see "0%" in the "Course overview" "block" Scenario: User complete activity and verify his progress Given I am on the "Test choice 1" "choice activity" page logged in as teacher1 And I navigate to "Settings" in current page administration And I set the following fields to these values: | Add requirements | 1 | | id_completionview | 1 | And I press "Save and return to course" And I log out When I am on the "My courses" page logged in as "student1" And I click on "All" "button" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" And I should see "0%" in the "Course overview" "block" And I am on the "Test choice 1" "choice activity" page And I am on the "My courses" page And I click on "All" "button" in the "Course overview" "block" And I should see "100%" in the "Course overview" "block" PK'9\F!smyoverview/settings.phpnu[. /** * Settings for the myoverview block * * @package block_myoverview * @copyright 2019 Tom Dickman * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { require_once($CFG->dirroot . '/blocks/myoverview/lib.php'); // Presentation options heading. $settings->add(new admin_setting_heading('block_myoverview/appearance', get_string('appearance', 'admin'), '')); // Display Course Categories on Dashboard course items (cards, lists, summary items). $settings->add(new admin_setting_configcheckbox( 'block_myoverview/displaycategories', get_string('displaycategories', 'block_myoverview'), get_string('displaycategories_help', 'block_myoverview'), 1)); // Enable / Disable available layouts. $choices = array(BLOCK_MYOVERVIEW_VIEW_CARD => get_string('card', 'block_myoverview'), BLOCK_MYOVERVIEW_VIEW_LIST => get_string('list', 'block_myoverview'), BLOCK_MYOVERVIEW_VIEW_SUMMARY => get_string('summary', 'block_myoverview')); $settings->add(new admin_setting_configmulticheckbox( 'block_myoverview/layouts', get_string('layouts', 'block_myoverview'), get_string('layouts_help', 'block_myoverview'), $choices, $choices)); unset ($choices); // Enable / Disable course filter items. $settings->add(new admin_setting_heading('block_myoverview/availablegroupings', get_string('availablegroupings', 'block_myoverview'), get_string('availablegroupings_desc', 'block_myoverview'))); $settings->add(new admin_setting_configcheckbox( 'block_myoverview/displaygroupingallincludinghidden', get_string('allincludinghidden', 'block_myoverview'), '', 0)); $settings->add(new admin_setting_configcheckbox( 'block_myoverview/displaygroupingall', get_string('all', 'block_myoverview'), '', 1)); $settings->add(new admin_setting_configcheckbox( 'block_myoverview/displaygroupinginprogress', get_string('inprogress', 'block_myoverview'), '', 1)); $settings->add(new admin_setting_configcheckbox( 'block_myoverview/displaygroupingpast', get_string('past', 'block_myoverview'), '', 1)); $settings->add(new admin_setting_configcheckbox( 'block_myoverview/displaygroupingfuture', get_string('future', 'block_myoverview'), '', 1)); $settings->add(new admin_setting_configcheckbox( 'block_myoverview/displaygroupingcustomfield', get_string('customfield', 'block_myoverview'), '', 0)); $choices = \core_customfield\api::get_fields_supporting_course_grouping(); if ($choices) { $choices = ['' => get_string('choosedots')] + $choices; $settings->add(new admin_setting_configselect( 'block_myoverview/customfiltergrouping', get_string('customfiltergrouping', 'block_myoverview'), '', '', $choices)); } else { $settings->add(new admin_setting_configempty( 'block_myoverview/customfiltergrouping', get_string('customfiltergrouping', 'block_myoverview'), get_string('customfiltergrouping_nofields', 'block_myoverview'))); } $settings->hide_if('block_myoverview/customfiltergrouping', 'block_myoverview/displaygroupingcustomfield'); $settings->add(new admin_setting_configcheckbox( 'block_myoverview/displaygroupingfavourites', get_string('favourites', 'block_myoverview'), '', 1)); $settings->add(new admin_setting_configcheckbox( 'block_myoverview/displaygroupinghidden', get_string('hiddencourses', 'block_myoverview'), '', 1)); } PK'9\6q ##completionstatus/db/access.phpnu[. /** * Completion status block caps. * * @package block_completionstatus * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/completionstatus:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\/completionstatus/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the completion status block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @since Moodle 2.0 * @package block_completionstatus * @copyright 2012 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Handles upgrading instances of this block. * * @param int $oldversion * @param object $block */ function xmldb_block_completionstatus_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\(3completionstatus/lang/en/block_completionstatus.phpnu[. /** * Strings for component 'block_completionstatus', language 'en', branch 'MOODLE_20_STABLE' * * @package block_completionstatus * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['completionprogressdetails'] = 'Completion progress details'; $string['completionstatus:addinstance'] = 'Add a new course completion status block'; $string['criteriagroup'] = 'Criteria group'; $string['firstofsecond'] = '{$a->first} of {$a->second}'; $string['pluginname'] = 'Course completion status'; $string['requirement'] = 'Requirement'; $string['returntocourse'] = 'Return to course'; $string['privacy:metadata'] = 'The Course completion status block only shows information about course completion and does not store any data of its own.'; PK'9\:6)completionstatus/version.phpnu[. /** * Version info * * @package block_completionstatus * @copyright 2009 Catalyst IT Ltd * @author Aaron Barnes * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_completionstatus'; $plugin->dependencies = ['report_completion' => 2025040800]; PK'9\-completionstatus/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_completionstatus. * * @package block_completionstatus * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_completionstatus\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_completionstatus implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\AAq 0completionstatus/tests/completionstatus_test.phpnu[. namespace block_completionstatus; use advanced_testcase; use block_completionstatus; use context_course; /** * PHPUnit block_completionstatus tests * * @package block_completionstatus * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_completionstatus */ final class completionstatus_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_completionstatus.php'); parent::setUpBeforeClass(); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_completionstatus(); // If blogs advanced feature is enabled, the method should return true. set_config('enablecompletion', true); $this->assertTrue($block->can_block_be_added($page)); // However, if the blogs advanced feature is disabled, the method should return false. set_config('enablecompletion', false); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\2SHcompletionstatus/tests/behat/block_completionstatus_manual_other.featurenu[@block @block_completionstatus Feature: Enable Block Completion in a course using manual completion by others In order to view the completion block in a course As a teacher I can add completion block to a course and set up manual completion by others Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | teacher2 | Teacher | 2 | teacher1@example.com | T2 | | student1 | Student | 1 | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | enablecompletion | | Course 1 | C1 | 0 | 1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | teacher2 | C1 | teacher | | student1 | C1 | student | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | completionstatus | Course | C1 | course-view-* | side-pre | Scenario: Add the block to a the course and mark a student complete. Given I am on the "Course 1" course page logged in as teacher1 And I navigate to "Course completion" in current page administration And I expand all fieldsets And I set the following fields to these values: | Teacher | 1 | And I press "Save changes" When I am on the "Course 1" course page logged in as student1 And I should see "Status: Not yet started" in the "Course completion status" "block" And I should see "No" in the "Teacher" "table_row" And I am on the "Course 1" course page logged in as teacher1 And I navigate to "Reports" in current page administration And I click on "Course completion" "link" in the "region-main" "region" And I follow "Click to mark user complete" # Running completion task just after clicking sometimes fail, as record # should be created before the task runs. And I wait "1" seconds And I run the scheduled task "core\task\completion_regular_task" And I am on site homepage And I am on the "Course 1" course page logged in as student1 Then I should see "Status: Complete" in the "Course completion status" "block" And I should see "Yes" in the "Teacher" "table_row" And I follow "More details" And I should see "Yes" in the "Marked complete by Teacher" "table_row" Scenario: Add the block to a the course and require multiple roles to mark a student complete. Given I am on the "Course 1" course page logged in as teacher1 And I navigate to "Course completion" in current page administration And I expand all fieldsets And I set the following fields to these values: | Teacher | 1 | | Non-editing teacher | 1 | | id_role_aggregation | ALL selected roles to mark when the condition is met | And I press "Save changes" When I am on the "Course 1" course page logged in as student1 And I should see "Status: Not yet started" in the "Course completion status" "block" And I should see "No" in the "Teacher" "table_row" And I should see "No" in the "Non-editing teacher" "table_row" And I am on the "Course 1" course page logged in as teacher1 And I navigate to "Reports" in current page administration And I click on "Course completion" "link" in the "region-main" "region" And I follow "Click to mark user complete" And I am on the "Course 1" course page logged in as student1 And I should see "Status: In progress" in the "Course completion status" "block" And I should see "Yes" in the "Teacher" "table_row" And I should see "No" in the "Non-editing teacher" "table_row" And I follow "More details" And I should see "Yes" in the "Marked complete by Teacher" "table_row" And I should see "No" in the "Marked complete by Non-editing teacher" "table_row" And I am on the "Course 1" course page logged in as teacher2 And I navigate to "Reports" in current page administration And I click on "Course completion" "link" in the "region-main" "region" And I follow "Click to mark user complete" # Running completion task just after clicking sometimes fail, as record # should be created before the task runs. And I wait "1" seconds And I run the scheduled task "core\task\completion_regular_task" And I am on site homepage And I am on the "Course 1" course page logged in as student1 Then I should see "Status: Complete" in the "Course completion status" "block" And I should see "Yes" in the "Teacher" "table_row" And I should see "Yes" in the "Non-editing teacher" "table_row" And I follow "More details" And I should see "Yes" in the "Marked complete by Teacher" "table_row" And I should see "Yes" in the "Marked complete by Non-editing teacher" "table_row" PK'9\M2? Gcompletionstatus/tests/behat/block_completionstatus_manual_self.featurenu[@block @block_completionstatus @block_selfcompletion Feature: Enable Block Completion in a course using manual self completion In order to view the completion block in a course As a teacher I can add completion block to a course and set up manual self completion Scenario: Add the block to a the course and manually complete the course Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | enablecompletion | | Course 1 | C1 | 0 | 1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And I enable "selfcompletion" "block" plugin And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | completionstatus | Course | C1 | course-view-* | side-pre | | selfcompletion | Course | C1 | course-view-* | side-pre | And I am on the "Course 1" course page logged in as teacher1 And I navigate to "Course completion" in current page administration And I expand all fieldsets And I set the following fields to these values: | id_criteria_self | 1 | And I press "Save changes" When I am on the "Course 1" course page logged in as student1 And I should see "Status: Not yet started" in the "Course completion status" "block" And I should see "No" in the "Self completion" "table_row" And I follow "Complete course" And I should see "Confirm self completion" And I press "Yes" And I should see "Status: In progress" in the "Course completion status" "block" # Running completion task just after clicking sometimes fail, as record # should be created before the task runs. And I wait "1" seconds And I run the scheduled task "core\task\completion_regular_task" And I am on "Course 1" course homepage Then I should see "Status: Complete" in the "Course completion status" "block" And I should see "Yes" in the "Self completion" "table_row" And I follow "More details" And I should see "Yes" in the "Self completion" "table_row" PK'9\uDLLOcompletionstatus/tests/behat/block_completionstatus_activity_completion.featurenu[@block @block_completionstatus @core_completion Feature: Enable Block Completion in a course using activity completion In order to view the completion block in a course As a teacher I can add completion block to a course and set up activity completion Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | | student2 | Student | 2 | student2@example.com | S2 | And the following "courses" exist: | fullname | shortname | category | enablecompletion | | Course 1 | C1 | 0 | 1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | And the following "activities" exist: | activity | course | idnumber | name | gradepass | completion | completionview | completionusegrade | completionpassgrade | | page | C1 | page1 | Test page name | | 2 | 1 | 0 | 0 | | assign | C1 | assign1 | Test assign name | 50 | 2 | 0 | 1 | 1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | completionstatus | Course | C1 | course-view-* | side-pre | And I am on the "Course 1" course page logged in as teacher1 And I change window size to "large" And I turn editing mode on Scenario: Completion status block when student has not started any activities Given I navigate to "Course completion" in current page administration And I expand all fieldsets And I set the following fields to these values: | Test page name | 1 | And I press "Save changes" When I am on the "Course 1" course page logged in as student1 Then I should see "Status: Not yet started" in the "Course completion status" "block" And I should see "0 of 1" in the "Activity completion" "table_row" Scenario: Completion status block when student has completed a page Given I navigate to "Course completion" in current page administration And I expand all fieldsets And I set the following fields to these values: | Test page name | 1 | And I press "Save changes" When I am on the "Test page name" "page activity" page logged in as student1 And I am on "Course 1" course homepage Then I should see "Status: Complete" in the "Course completion status" "block" And I should see "1 of 1" in the "Activity completion" "table_row" And I follow "More details" And I should see "Yes" in the "Activity completion" "table_row" Scenario: Completion status block with items with passing grade Given I navigate to "Course completion" in current page administration And I expand all fieldsets And I set the following fields to these values: | Test assign name | 1 | And I press "Save changes" And the following "grade grades" exist: | gradeitem | user | grade | | Test assign name | student1 | 53 | When I am on the "Course 1" course page logged in as student1 Then I should see "Status: Complete" in the "Course completion status" "block" And I should see "1 of 1" in the "Activity completion" "table_row" And I trigger cron And I am on "Course 1" course homepage And I follow "More details" And I should see "Achieving grade, Achieving passing grade" in the "Activity completion" "table_row" And I should see "Yes" in the "Activity completion" "table_row" Scenario: Completion status block with items with failing grade Given the following "grade grades" exist: | gradeitem | user | grade | | Test assign name | student1 | 49 | And I navigate to "Course completion" in current page administration And I expand all fieldsets And I set the following fields to these values: | Test assign name | 1 | And I press "Save changes" When I am on the "Course 1" course page logged in as student1 Then I should see "Status: Not yet started" in the "Course completion status" "block" And I should see "0 of 1" in the "Activity completion" "table_row" And I trigger cron And I am on "Course 1" course homepage And I follow "More details" And I should see "Achieving grade, Achieving passing grade" in the "Activity completion" "table_row" And I should see "No" in the "Activity completion" "table_row" @javascript Scenario: Student visibility respects combined activity and section restrictions with progressive completion Given the following "activities" exist: | activity | name | intro | course | idnumber | section | visible | completion | completionview | | page | task A | page description | C1 | page1 | 0 | 1 | 2 | 1 | | page | task B | page description | C1 | page2 | 1 | 1 | 2 | 1 | | assign | task C | assignment description | C1 | assign1 | 1 | 1 | 2 | 1 | And I navigate to "Course completion" in current page administration And I expand all fieldsets And I set the following fields to these values: | Page - task A | 1 | | Page - task B | 1 | | Assignment - task C | 1 | And I press "Save changes" # Add conditionally visible restriction (open eye) to section 1 requiring task A completion. And I turn editing mode on And I edit the section "1" And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I set the field "Activity or resource" to "task A" And I press "Save changes" # Add conditionally hidden restriction (closed eye) to task C requiring task A completion. And I am on the "task C" "assign activity editing" page And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on "Item name displayed with access restriction information if student doesn't meet this condition • Click to hide" "link" And I set the field "Activity or resource" to "task A" And I press "Save and return to course" And I log out # Initial state: Only unrestricted visible activities appear. When I am on the "Course 1" course page logged in as student1 Then I should see "Status: Not yet started" And I should see "0 of 1" in the "Activity completion" "table_row" And I follow "More details" And I should see "task A" in the "criteriastatus" "table" And I should not see "task B" in the "criteriastatus" "table" And I should not see "task C" in the "criteriastatus" "table" # After completing task A: Section 2 activities become visible. And I click on "task A" "link" And I am on the "Course 1" course page logged in as student1 And I should see "Status: In progress" And I should see "1 of 3" in the "Activity completion" "table_row" And I follow "More details" And I should see "task A" in the "criteriastatus" "table" And I should see "task B" in the "criteriastatus" "table" And I should see "task C" in the "criteriastatus" "table" @javascript Scenario: Student completion view shows only accessible activities considering all activity restrictions Given the following "activities" exist: | activity | name | intro | course | idnumber | section | visible | completion | completionview | | page | task A | page description | C1 | page1 | 0 | 1 | 2 | 1 | | page | task B | page description | C1 | assign1 | 1 | 1 | 2 | 1 | | assign | task C | assignment description | C1 | assign2 | 1 | 1 | 2 | 1 | And I navigate to "Course completion" in current page administration And I expand all fieldsets # Set completion of the activities. And I set the following fields to these values: | Page - task A | 1 | | Page - task B | 1 | | Assignment - task C | 1 | And I press "Save changes" # Add conditionally visible restriction (open eye) to "task B". And I am on the "task B" "page activity editing" page And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I set the field "Activity or resource" to "task A" And I press "Save and return to course" # Add conditionally hidden restriction (closed eye) to "task C". And I am on the "task C" "assign activity editing" page And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on "Item name displayed with access restriction information if student doesn't meet this condition • Click to hide" "link" And I set the field "Activity or resource" to "task A" And I press "Save and return to course" When I am on the "Course 1" course page logged in as student1 And I should see "Status: Not yet started" And I should see "0 of 2" in the "Activity completion" "table_row" And I follow "More details" Then I should see "task A" in the "criteriastatus" "table" And I should see "task B" in the "criteriastatus" "table" And I should not see "task C" in the "criteriastatus" "table" And I click on "task A" "link" # Complete task A to make task C visible. And I am on the "Course 1" course page logged in as student1 And I should see "Status: In progress" And I should see "1 of 3" in the "Activity completion" "table_row" And I follow "More details" And I should see "task A" in the "criteriastatus" "table" And I should see "task B" in the "criteriastatus" "table" And I should see "task C" in the "criteriastatus" "table" @javascript Scenario: Hidden activities do not appear in the completion status block Given the following "activities" exist: | activity | name | intro | course | idnumber | section | visible | completion | completionview | | page | task A | page description | C1 | page1 | 0 | 1 | 2 | 1 | | page | task B | page description | C1 | page2 | 0 | 0 | 2 | 1 | And I navigate to "Course completion" in current page administration And I expand all fieldsets # Set completion of the activities. And I set the following fields to these values: | Page - task A | 1 | | Page - task B | 1 | And I press "Save changes" When I am on the "Course 1" course page logged in as student1 And I should see "Status: Not yet started" And I should see "0 of 1" in the "Activity completion" "table_row" And I follow "More details" And I should see "task A" in the "criteriastatus" "table" Then I should not see "task B" in the "criteriastatus" "table" @javascript Scenario: Activities in the hidden section do not appear in the completion status block Given the following "activities" exist: | activity | name | intro | course | idnumber | section | visible | completion | completionview | | page | task A | page description | C1 | page1 | 0 | 1 | 2 | 1 | | page | task B | page description | C1 | page2 | 1 | 1 | 2 | 1 | | assign | task C | page description | C1 | page3 | 2 | 1 | 2 | 1 | | assign | task D | page description | C1 | page4 | 3 | 1 | 2 | 1 | And I navigate to "Course completion" in current page administration And I expand all fieldsets # Set completion of the activities. And I set the following fields to these values: | Page - task A | 1 | | Page - task B | 1 | | Assignment - task C | 1 | | Assignment - task D | 1 | And I press "Save changes" # Hide section 1 to make sure book activities are not visible to the student. And I hide section "1" # Add conditionally visible restriction to section 2. And I edit the section "2" And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I set the field "Activity or resource" to "task A" And I press "Save changes" # Add conditionally hidden restriction to section 3. And I am on the "Course 1" course page And I edit the section "3" And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I set the field "Activity or resource" to "task A" And I click on "Item name displayed with access restriction information if student doesn't meet this condition • Click to hide" "link" And I press "Save changes" And I log out When I am on the "Course 1" course page logged in as student1 And I should see "Status: Not yet started" And I should see "0 of 1" in the "Activity completion" "table_row" And I follow "More details" Then I should see "task A" in the "criteriastatus" "table" And I should not see "task B" in the "criteriastatus" "table" And I should not see "task C" in the "criteriastatus" "table" And I should not see "task D" in the "criteriastatus" "table" # Complete task A to make other activities visible. And I click on "task A" "link" And I am on the "Course 1" course page logged in as student1 And I should see "Status: In progress" And I should see "1 of 3" in the "Activity completion" "table_row" And I follow "More details" And I should see "task A" in the "criteriastatus" "table" And I should not see "task B" in the "criteriastatus" "table" And I should see "task C" in the "criteriastatus" "table" And I should see "task D" in the "criteriastatus" "table" @javascript Scenario: Activities with disabled completion tracking are omitted from the completion view Given the following "activities" exist: | activity | name | intro | course | idnumber | section | visible | completion | completionview | | page | task A | page description | C1 | page1 | 0 | 1 | 2 | 1 | | page | task B | page description | C1 | page2 | 1 | 1 | 2 | 1 | And I navigate to "Course completion" in current page administration And I expand all fieldsets # Set completion of the activities. And I set the following fields to these values: | Page - task A | 1 | | Page - task B | 1 | And I press "Save changes" # Disable completion tracking for "task A". And I am on the "task A" "page activity editing" page And I expand all fieldsets And I set the following fields to these values: | completion | 0 | And I press "Save and return to course" When I am on the "Course 1" course page logged in as student1 And I should see "Status: Not yet started" And I should see "0 of 1" in the "Activity completion" "table_row" And I follow "More details" Then I should not see "task A" in the "criteriastatus" "table" And I should see "task B" in the "criteriastatus" "table" @javascript Scenario: Activities with group or grouping restrictions are omitted from the completion view Given the following "activities" exist: | activity | name | intro | course | idnumber | section | visible | completion | completionview | | page | task A | page description | C1 | page1 | 0 | 1 | 2 | 1 | | page | task B | page description | C1 | page2 | 1 | 1 | 2 | 1 | | page | task C | page description | C1 | page3 | 2 | 1 | 2 | 1 | And I navigate to "Course completion" in current page administration And I expand all fieldsets # Set completion of the activities. And I set the following fields to these values: | Page - task A | 1 | | Page - task B | 1 | | Page - task C | 1 | And I press "Save changes" # Add groups and groupings. And the following "groups" exist: | name | course | idnumber | | G1 | C1 | GI1 | | G2 | C1 | GI2 | And the following "groupings" exist: | name | course | idnumber | | Grouping 1 | C1 | GG1 | | Grouping 2 | C1 | GG2 | And the following "grouping groups" exist: | grouping | group | | GG1 | GI1 | | GG2 | GI2 | # Add students to groups. And the following "group members" exist: | user | group | | student1 | GI1 | | student2 | GI2 | # Add group restriction to "task B". And I am on the "task B" "page activity editing" page And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Group" "button" in the "Add restriction..." "dialogue" And I set the field "Group" to "G1" And I click on "Save and return to course" "button" # Add Grouping and 'Activity or resource' restriction to "task C". And I am on the "task C" "page activity editing" page And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Grouping" "button" in the "Add restriction..." "dialogue" And I set the field "Grouping" to "Grouping 2" And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I set the field "Activity or resource" to "task A" And I click on "Save and return to course" "button" When I am on the "Course 1" course page logged in as student1 And I should see "Status: Not yet started" And I should see "0 of 2" in the "Activity completion" "table_row" And I follow "More details" Then I should see "task A" in the "criteriastatus" "table" And I should see "task B" in the "criteriastatus" "table" And I should not see "task C" in the "criteriastatus" "table" And I am on the "Course 1" course page logged in as student2 And I should see "Status: Not yet started" And I should see "0 of 2" in the "Activity completion" "table_row" And I follow "More details" And I should see "task A" in the "criteriastatus" "table" And I should not see "task B" in the "criteriastatus" "table" And I should see "task C" in the "criteriastatus" "table" PK'9\rwy y ;completionstatus/tests/behat/block_completionstatus.featurenu[@block @block_completionstatus Feature: Enable Block Completion in a course In order to view the completion block in a course As a teacher I can add completion block to a course Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | enablecompletion | | Course 1 | C1 | 0 | 1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | Scenario: Add the block to a the course where completion is disabled Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I navigate to "Settings" in current page administration And I set the following fields to these values: | Enable completion tracking | No | And I press "Save and display" When I add the "Course completion status" block Then I should see "Completion is not enabled for this course" in the "Course completion status" "block" Scenario: Add the block to a the course where completion is not set Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Course completion status" block Then I should see "No completion criteria set for this course" in the "Course completion status" "block" Scenario: Add the block to a course with criteria and view as an untracked role. Given the following "activities" exist: | activity | course | idnumber | name | intro | | page | C1 | page1 | Test page name | Test page description | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I follow "Test page name" And I navigate to "Settings" in current page administration And I set the following fields to these values: | Add requirements | 1 | | View the activity | 1 | And I press "Save and return to course" When I add the "Course completion status" block And I navigate to "Course completion" in current page administration And I expand all fieldsets And I set the following fields to these values: | Test page name | 1 | And I press "Save changes" Then I should see "You are currently not being tracked by completion in this course" in the "Course completion status" "block" PK'9\8((+completionstatus/block_completionstatus.phpnu[. /** * Block for displayed logged in user's course completion status. * Displays overall, and individual criteria status for logged in user. * * @package block_completionstatus * @copyright 2009-2012 Catalyst IT Ltd * @author Aaron Barnes * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_completionstatus extends block_base { public function init() { global $CFG; require_once("{$CFG->libdir}/completionlib.php"); $this->title = get_string('pluginname', 'block_completionstatus'); } public function applicable_formats() { return array('course' => true); } public function get_content() { global $USER; $rows = array(); $srows = array(); $prows = array(); // If content is cached. if ($this->content !== null) { return $this->content; } $course = $this->page->course; $context = context_course::instance($course->id); // Create empty content. $this->content = new stdClass(); $this->content->text = ''; $this->content->footer = ''; // Can edit settings? $can_edit = has_capability('moodle/course:update', $context); // Get course completion data. $info = new completion_info($course); // Don't display if completion isn't enabled! if (!completion_info::is_enabled_for_site()) { if ($can_edit) { $this->content->text .= get_string('completionnotenabledforsite', 'completion'); } return $this->content; } else if (!$info->is_enabled()) { if ($can_edit) { $this->content->text .= get_string('completionnotenabledforcourse', 'completion'); } return $this->content; } // Load criteria to display. $completions = $info->get_completions($USER->id); // Check if this course has any criteria. if (empty($completions)) { if ($can_edit) { $this->content->text .= get_string('nocriteriaset', 'completion'); } return $this->content; } // Check this user is enroled. if ($info->is_tracked_user($USER->id)) { // Generate markup for criteria statuses. $data = ''; // For aggregating activity completion. $activities = array(); $activities_complete = 0; // For aggregating course prerequisites. $prerequisites = array(); $prerequisites_complete = 0; // Flag to set if current completion data is inconsistent with what is stored in the database. $pending_update = false; // Get activities visible to the user that have completion enabled. $visibleactivities = $info->get_user_activities_with_completion($USER->id); // Loop through course criteria. foreach ($completions as $completion) { $criteria = $completion->get_criteria(); $complete = $completion->is_complete(); if (!$pending_update && $criteria->is_pending($completion)) { $pending_update = true; } // Activities are a special case, so cache them and leave them till last. if ($criteria->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) { // Only include activities that are visible to the user. if (!isset($visibleactivities[$criteria->moduleinstance])) { continue; } $activities[$criteria->moduleinstance] = $complete; if ($complete) { $activities_complete++; } continue; } // Prerequisites are also a special case, so cache them and leave them till last. if ($criteria->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) { $prerequisites[$criteria->courseinstance] = $complete; if ($complete) { $prerequisites_complete++; } continue; } $row = new html_table_row(); $row->cells[0] = new html_table_cell($criteria->get_title()); $row->cells[1] = new html_table_cell($completion->get_status()); $row->cells[1]->style = 'text-align: right;'; $srows[] = $row; } // Aggregate activities. if (!empty($activities)) { $a = new stdClass(); $a->first = $activities_complete; $a->second = count($activities); $row = new html_table_row(); $row->cells[0] = new html_table_cell(get_string('activitiescompleted', 'completion')); $row->cells[1] = new html_table_cell(get_string('firstofsecond', 'block_completionstatus', $a)); $row->cells[1]->style = 'text-align: right;'; $srows[] = $row; } // Aggregate prerequisites. if (!empty($prerequisites)) { $a = new stdClass(); $a->first = $prerequisites_complete; $a->second = count($prerequisites); $row = new html_table_row(); $row->cells[0] = new html_table_cell(get_string('dependenciescompleted', 'completion')); $row->cells[1] = new html_table_cell(get_string('firstofsecond', 'block_completionstatus', $a)); $row->cells[1]->style = 'text-align: right;'; $prows[] = $row; $srows = array_merge($prows, $srows); } // Display completion status. $table = new html_table(); $table->width = '100%'; $table->attributes = array('style'=>'font-size: 90%;', 'class'=>''); $row = new html_table_row(); $content = html_writer::tag('b', get_string('status').': '); // Is course complete? $coursecomplete = $info->is_course_complete($USER->id); // Load course completion. $params = array( 'userid' => $USER->id, 'course' => $course->id ); $ccompletion = new completion_completion($params); // Has this user completed any criteria? $criteriacomplete = $info->count_course_user_data($USER->id); if ($pending_update) { $content .= html_writer::tag('i', get_string('pending', 'completion')); } else if ($coursecomplete) { $content .= get_string('complete'); } else if (!$criteriacomplete && !$ccompletion->timestarted) { $content .= html_writer::tag('i', get_string('notyetstarted', 'completion')); } else { $content .= html_writer::tag('i', get_string('inprogress', 'completion')); } $row->cells[0] = new html_table_cell($content); $row->cells[0]->colspan = '2'; $rows[] = $row; $row = new html_table_row(); $content = ""; // Get overall aggregation method. $overall = $info->get_aggregation_method(); if ($overall == COMPLETION_AGGREGATION_ALL) { $content .= get_string('criteriarequiredall', 'completion'); } else { $content .= get_string('criteriarequiredany', 'completion'); } $content .= ':'; $row->cells[0] = new html_table_cell($content); $row->cells[0]->colspan = '2'; $rows[] = $row; $row = new html_table_row(); $row->cells[0] = new html_table_cell(html_writer::tag('b', get_string('requiredcriteria', 'completion'))); $row->cells[1] = new html_table_cell(html_writer::tag('b', get_string('status'))); $row->cells[1]->style = 'text-align: right;'; $rows[] = $row; // Array merge $rows and $data here. $rows = array_merge($rows, $srows); $table->data = $rows; $this->content->text .= html_writer::table($table); // Display link to detailed view. $details = new moodle_url('/blocks/completionstatus/details.php', array('course' => $course->id)); $this->content->footer .= html_writer::link($details, get_string('moredetails', 'completion')); } else { // If user is not enrolled, show error. $this->content->text = get_string('nottracked', 'completion'); } if (has_capability('report/completion:view', $context)) { $report = new moodle_url('/report/completion/index.php', array('course' => $course->id)); if (empty($this->content->footer)) { $this->content->footer = ''; } $this->content->footer .= html_writer::empty_tag('br'); $this->content->footer .= html_writer::link($report, get_string('viewcoursereport', 'completion')); } return $this->content; } /** * This block shouldn't be added to a page if the completion tracking advanced feature is disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { global $CFG; return $CFG->enablecompletion; } } PK'9\X''completionstatus/details.phpnu[. /** * Block for displaying logged in user's course completion status * * @package block_completionstatus * @copyright 2009-2012 Catalyst IT Ltd * @author Aaron Barnes * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__.'/../../config.php'); require_once("{$CFG->libdir}/completionlib.php"); // Load data. $id = required_param('course', PARAM_INT); $userid = optional_param('user', 0, PARAM_INT); // Load course. $course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST); // Load user. if ($userid) { $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); } else { $user = $USER; } // Check permissions. require_login($course); if (!completion_can_view_data($user->id, $course)) { throw new \moodle_exception('cannotviewreport'); } // Load completion data. $info = new completion_info($course); $returnurl = new moodle_url('/course/view.php', array('id' => $id)); // Don't display if completion isn't enabled. if (!$info->is_enabled()) { throw new \moodle_exception('completionnotenabled', 'completion', $returnurl); } // Check this user is enroled. if (!$info->is_tracked_user($user->id)) { if ($USER->id == $user->id) { throw new \moodle_exception('notenroled', 'completion', $returnurl); } else { throw new \moodle_exception('usernotenroled', 'completion', $returnurl); } } // Print header. $page = get_string('completionprogressdetails', 'block_completionstatus'); $title = format_string($course->fullname) . ': ' . $page; $PAGE->navbar->add($page); $PAGE->set_pagelayout('report'); $PAGE->set_url('/blocks/completionstatus/details.php', array('course' => $course->id, 'user' => $user->id)); $PAGE->set_title(get_string('course') . ': ' . $course->fullname); $PAGE->set_heading($title); echo $OUTPUT->header(); // Display completion status. echo html_writer::start_tag('table', ['class' => 'generalbox table-reboot']); echo html_writer::start_tag('tbody'); // If not display logged in user, show user name. if ($USER->id != $user->id) { echo html_writer::start_tag('tr'); echo html_writer::start_tag('td', array('colspan' => '2')); echo html_writer::tag('b', get_string('showinguser', 'completion') . ' '); $url = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $course->id)); echo html_writer::link($url, fullname($user)); echo html_writer::end_tag('td'); echo html_writer::end_tag('tr'); } echo html_writer::start_tag('tr'); echo html_writer::start_tag('td', array('colspan' => '2')); echo html_writer::tag('b', get_string('status') . ' '); // Is course complete? $coursecomplete = $info->is_course_complete($user->id); // Has this user completed any criteria? $criteriacomplete = $info->count_course_user_data($user->id); // Load course completion. $params = array( 'userid' => $user->id, 'course' => $course->id, ); $ccompletion = new completion_completion($params); // Save row data. $rows = array(); // Flag to set if current completion data is inconsistent with what is stored in the database. $pendingupdate = false; // Load criteria to display. $completions = $info->get_completions($user->id); // Get activities visible to the user that have completion enabled. $visibleactivities = $info->get_user_activities_with_completion($user->id); // Loop through course criteria. foreach ($completions as $completion) { $criteria = $completion->get_criteria(); // Skip display of activity completion criteria for activities the user cannot see. if ( $criteria->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY && !isset($visibleactivities[$criteria->moduleinstance]) ) { continue; } if (!$pendingupdate && $criteria->is_pending($completion)) { $pendingupdate = true; } $row = array(); $row['type'] = $criteria->criteriatype; $row['title'] = $criteria->get_title(); $row['status'] = $completion->get_status(); $row['complete'] = $completion->is_complete(); $row['timecompleted'] = $completion->timecompleted; $row['details'] = $criteria->get_details($completion); $rows[] = $row; } if ($pendingupdate) { echo html_writer::tag('i', get_string('pending', 'completion')); } else if ($coursecomplete) { echo get_string('complete'); } else if (!$criteriacomplete && !$ccompletion->timestarted) { echo html_writer::tag('i', get_string('notyetstarted', 'completion')); } else { echo html_writer::tag('i', get_string('inprogress', 'completion')); } echo html_writer::end_tag('td'); echo html_writer::end_tag('tr'); // Check if this course has any criteria. if (empty($completions)) { echo html_writer::start_tag('tr'); echo html_writer::start_tag('td', array('colspan' => '2')); echo html_writer::start_tag('br'); echo $OUTPUT->box(get_string('nocriteriaset', 'completion'), 'noticebox'); echo html_writer::end_tag('td'); echo html_writer::end_tag('tr'); echo html_writer::end_tag('tbody'); echo html_writer::end_tag('table'); } else { echo html_writer::start_tag('tr'); echo html_writer::start_tag('td', array('colspan' => '2')); echo html_writer::tag('b', get_string('required') . ' '); // Get overall aggregation method. $overall = $info->get_aggregation_method(); if ($overall == COMPLETION_AGGREGATION_ALL) { echo get_string('criteriarequiredall', 'completion'); } else { echo get_string('criteriarequiredany', 'completion'); } echo html_writer::end_tag('td'); echo html_writer::end_tag('tr'); echo html_writer::end_tag('tbody'); echo html_writer::end_tag('table'); // Generate markup for criteria statuses. echo html_writer::start_tag('table', [ 'class' => 'generalbox logtable table-reboot', 'id' => 'criteriastatus', 'width' => '100%', ] ); echo html_writer::start_tag('tbody'); echo html_writer::start_tag('tr', array('class' => 'ccheader')); echo html_writer::tag('th', get_string('criteriagroup', 'block_completionstatus'), array('class' => 'c0 header', 'scope' => 'col')); echo html_writer::tag('th', get_string('criteria', 'completion'), array('class' => 'c1 header', 'scope' => 'col')); echo html_writer::tag('th', get_string('requirement', 'block_completionstatus'), array('class' => 'c2 header', 'scope' => 'col')); echo html_writer::tag('th', get_string('status'), array('class' => 'c3 header', 'scope' => 'col')); echo html_writer::tag('th', get_string('complete'), array('class' => 'c4 header', 'scope' => 'col')); echo html_writer::tag('th', get_string('completiondate', 'report_completion'), array('class' => 'c5 header', 'scope' => 'col')); echo html_writer::end_tag('tr'); // Print table. $last_type = ''; $agg_type = false; $oddeven = 0; foreach ($rows as $row) { echo html_writer::start_tag('tr', array('class' => 'r' . $oddeven)); // Criteria group. echo html_writer::start_tag('td', array('class' => 'cell c0')); if ($last_type !== $row['details']['type']) { $last_type = $row['details']['type']; echo $last_type; // Reset agg type. $agg_type = true; } else { // Display aggregation type. if ($agg_type) { $agg = $info->get_aggregation_method($row['type']); echo '('. html_writer::start_tag('i'); if ($agg == COMPLETION_AGGREGATION_ALL) { echo core_text::strtolower(get_string('all', 'completion')); } else { echo core_text::strtolower(get_string('any', 'completion')); } echo ' ' . html_writer::end_tag('i') .core_text::strtolower(get_string('required')).')'; $agg_type = false; } } echo html_writer::end_tag('td'); // Criteria title. echo html_writer::start_tag('td', array('class' => 'cell c1')); echo $row['details']['criteria']; echo html_writer::end_tag('td'); // Requirement. echo html_writer::start_tag('td', array('class' => 'cell c2')); echo $row['details']['requirement']; echo html_writer::end_tag('td'); // Status. echo html_writer::start_tag('td', array('class' => 'cell c3')); echo $row['details']['status']; echo html_writer::end_tag('td'); // Is complete. echo html_writer::start_tag('td', array('class' => 'cell c4')); echo $row['complete'] ? get_string('yes') : get_string('no'); echo html_writer::end_tag('td'); // Completion data. echo html_writer::start_tag('td', array('class' => 'cell c5')); if ($row['timecompleted']) { echo userdate($row['timecompleted'], get_string('strftimedatemonthtimeshort', 'langconfig')); } else { echo '-'; } echo html_writer::end_tag('td'); echo html_writer::end_tag('tr'); // For row striping. $oddeven = $oddeven ? 0 : 1; } echo html_writer::end_tag('tbody'); echo html_writer::end_tag('table'); } echo $OUTPUT->footer(); PK'9\C{{recent_activity/styles.cssnu[.block_recent_activity .unlist li { margin-bottom: 1em; } .block_recent_activity li .head .date { float: right; } PK'9\c ;;recent_activity/renderer.phpnu[. /** * Renderer for block recent_activity * * @package block_recent_activity * @copyright 2012 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; /** * recent_activity block rendrer * * @package block_recent_activity * @copyright 2012 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_recent_activity_renderer extends plugin_renderer_base { /** * Renders HTML to display recent_activity block * * @param stdClass $course * @param int $timestart * @param array $recentenrolments array of changes in enrolments * @param array $structuralchanges array of changes in course structure * @param array $modulesrecentactivity array of changes in modules (provided by modules) * @return string */ public function recent_activity($course, $timestart, $recentenrolments, $structuralchanges, $modulesrecentactivity) { $output = html_writer::tag('div', get_string('activitysince', '', userdate($timestart)), array('class' => 'activityhead')); $output .= html_writer::tag('div', html_writer::link(new moodle_url('/course/recent.php', array('id' => $course->id)), get_string('recentactivityreport')), array('class' => 'activityhead mb-3')); $content = false; // Firstly, have there been any new enrolments? if ($recentenrolments) { $content = true; $context = context_course::instance($course->id); $viewfullnames = has_capability('moodle/site:viewfullnames', $context); $output .= html_writer::start_tag('div', array('class' => 'newusers')); $output .= $this->heading(get_string("newusers").':', 3); //Accessibility: new users now appear in an
                list. $output .= html_writer::start_tag('ol', array('class' => 'list')); foreach ($recentenrolments as $user) { $output .= html_writer::tag('li', html_writer::link(new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $course->id)), fullname($user, $viewfullnames)), array('class' => 'name')); } $output .= html_writer::end_tag('ol'); $output .= html_writer::end_tag('div'); } // Next, have there been any modifications to the course structure? if (!empty($structuralchanges)) { $content = true; $output .= $this->heading(get_string("courseupdates") . ':', 6); foreach ($structuralchanges as $changeinfo => $change) { $output .= $this->structural_change($change); } } // Now display new things from each module foreach ($modulesrecentactivity as $modname => $moduleactivity) { $content = true; $output .= $moduleactivity; } if (! $content) { $output .= html_writer::tag('p', get_string('nothingnew'), array('class' => 'message')); } return $output; } /** * Renders HTML for one change in course structure * * @see block_recent_activity::get_structural_changes() * @param array $change array containing attributes * 'action' - one of: 'add mod', 'update mod', 'delete mod' * 'module' - instance of cm_info (for 'delete mod' it is an object with attributes modname and modfullname) * @return string */ protected function structural_change($change) { $cm = $change['module']; switch ($change['action']) { case 'delete mod': $text = get_string('deletedactivity', 'moodle', $cm->modfullname); break; case 'add mod': $text = get_string('added', 'moodle', $cm->modfullname). '
                '. html_writer::link($cm->url, format_string($cm->name, true)); break; case 'update mod': $text = get_string('updated', 'moodle', $cm->modfullname). '
                '. html_writer::link($cm->url, format_string($cm->name, true)); break; default: return ''; } return html_writer::tag('p', $text, array('class' => 'activity')); } } PK'9\]recent_activity/db/tasks.phpnu[. /** * Task definition for block_recent_activity. * @author Farhan Karmali * @copyright Farhan Karmali 2018 * @package block_recent_activity * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $tasks = array( array( 'classname' => '\block_recent_activity\task\cleanup', 'blocking' => 0, 'minute' => 'R', 'hour' => 'R', 'day' => '*', 'month' => '*', 'dayofweek' => '*', 'disabled' => 0 ) ); PK'9\aZrecent_activity/db/access.phpnu[. /** * Recent activity block caps. * * @package block_recent_activity * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/recent_activity:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), 'block/recent_activity:viewaddupdatemodule' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'user' => CAP_ALLOW ) ), 'block/recent_activity:viewdeletemodule' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'user' => CAP_ALLOW ) ) ); PK'9\recent_activity/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the recent activity block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @package block_recent_activity * @copyright 2014 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade code for the recent activity block. * * @param int $oldversion * @param object $block */ function xmldb_block_recent_activity_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\[P{{recent_activity/db/install.xmlnu[
                PK'9\recent_activity/db/events.phpnu[. /** * Event observer. * * @package block_recent_activity * @category event * @copyright 2014 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $observers = array ( array ( 'eventname' => '\core\event\course_module_created', 'callback' => 'block_recent_activity_observer::store', 'internal' => false, // This means that we get events only after transaction commit. 'priority' => 1000, ), array ( 'eventname' => '\core\event\course_module_updated', 'callback' => 'block_recent_activity_observer::store', 'internal' => false, // This means that we get events only after transaction commit. 'priority' => 1000, ), array ( 'eventname' => '\core\event\course_module_deleted', 'callback' => 'block_recent_activity_observer::store', 'internal' => false, // This means that we get events only after transaction commit. 'priority' => 1000, ), ); PK'9\()ՎSS1recent_activity/lang/en/block_recent_activity.phpnu[. /** * Strings for component 'block_recent_activity', language 'en', branch 'MOODLE_20_STABLE' * * @package block_recent_activity * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['cleanuptask'] = 'Cleanup task for recent activity block'; $string['pluginname'] = 'Recent activity'; $string['privacy:metadata'] = 'The recent activity block contains a cache of data stored elsewhere in Moodle.'; $string['privacy:metadata:block_recent_activity'] = 'Temporary log of recent teacher activity. Removed after two days'; $string['privacy:metadata:block_recent_activity:action'] = 'Action: created, updated or deleted'; $string['privacy:metadata:block_recent_activity:cmid'] = 'Course activity ID'; $string['privacy:metadata:block_recent_activity:courseid'] = 'Course ID'; $string['privacy:metadata:block_recent_activity:modname'] = 'Module type name (for delete action)'; $string['privacy:metadata:block_recent_activity:timecreated'] = 'Time when action was performed'; $string['privacy:metadata:block_recent_activity:userid'] = 'User performing the action'; $string['recent_activity:addinstance'] = 'Add a new recent activity block'; $string['recent_activity:viewaddupdatemodule'] = 'View added and updated modules in recent activity block'; $string['recent_activity:viewdeletemodule'] = 'View deleted modules in recent activity block'; PK'9\(S~recent_activity/version.phpnu[. /** * Version details * * @package block_recent_activity * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_recent_activity'; // Full name of the plugin (used for diagnostics) PK'9\*u,recent_activity/classes/privacy/provider.phpnu[. /** * Privacy subsystem implementation for block_recent_activity. * * @package block_recent_activity * @category privacy * @copyright 2018 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recent_activity\privacy; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\contextlist; use core_privacy\local\request\userlist; use core_privacy\local\request\approved_userlist; defined('MOODLE_INTERNAL') || die(); /** * The block_recent_activity does not keep any data for more than COURSE_MAX_RECENT_PERIOD. * * @copyright 2018 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns metadata. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { // This plugin defines a db table but it is not considered personal data and, therefore, not exported or deleted. $collection->add_database_table('block_recent_activity', [ 'courseid' => 'privacy:metadata:block_recent_activity:courseid', 'cmid' => 'privacy:metadata:block_recent_activity:cmid', 'timecreated' => 'privacy:metadata:block_recent_activity:timecreated', 'userid' => 'privacy:metadata:block_recent_activity:userid', 'action' => 'privacy:metadata:block_recent_activity:action', 'modname' => 'privacy:metadata:block_recent_activity:modname' ], 'privacy:metadata:block_recent_activity'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { return new contextlist(); } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { } /** * Delete all data for all users in the specified context. * * @param \context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(\context $context) { } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { } } PK'9\zk* * $recent_activity/classes/observer.phpnu[. /** * Event observer. * * @package block_recent_activity * @copyright 2014 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Event observer. * Stores all actions about modules create/update/delete in plugin own's table. * This allows the block to avoid expensive queries to the log table. * * @package block_recent_activity * @copyright 2014 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_recent_activity_observer { /** @var int indicates that course module was created */ const CM_CREATED = 0; /** @var int indicates that course module was udpated */ const CM_UPDATED = 1; /** @var int indicates that course module was deleted */ const CM_DELETED = 2; /** * Store all actions about modules create/update/delete in own table. * * @param \core\event\base $event */ public static function store(\core\event\base $event) { global $DB; $eventdata = new \stdClass(); switch ($event->eventname) { case '\core\event\course_module_created': $eventdata->action = self::CM_CREATED; break; case '\core\event\course_module_updated': $eventdata->action = self::CM_UPDATED; break; case '\core\event\course_module_deleted': $eventdata->action = self::CM_DELETED; $eventdata->modname = $event->other['modulename']; break; default: return; } $eventdata->timecreated = $event->timecreated; $eventdata->courseid = $event->courseid; $eventdata->cmid = $event->objectid; $eventdata->userid = $event->userid; $DB->insert_record('block_recent_activity', $eventdata); } } PK'9\#(recent_activity/classes/task/cleanup.phpnu[. /** * Task for updating RSS feeds for rss client block * * @package block_recent_activity * @author Farhan Karmali * @copyright Farhan Karmali 2018 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recent_activity\task; defined('MOODLE_INTERNAL') || die(); /** * Task for updating RSS feeds for rss client block * * @package block_recent_activity * @author Farhan Karmali * @copyright Farhan Karmali 2018 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cleanup extends \core\task\scheduled_task { /** * Name for this task. * * @return string */ public function get_name() { return get_string('cleanuptask', 'block_recent_activity'); } /** * Remove old entries from table block_recent_activity */ public function execute() { global $CFG, $DB; require_once("{$CFG->dirroot}/course/lib.php"); // Those entries will never be displayed as RECENT anyway. $DB->delete_records_select('block_recent_activity', 'timecreated < ?', [ time() - COURSE_MAX_RECENT_PERIOD, ]); } } PK'9\ V)V)6recent_activity/tests/behat/structural_changes.featurenu[@block @block_recent_activity Feature: View structural changes in recent activity block In order to know when activities were changed As a user In need to see the structural changes in recent activity block Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Terry1 | Teacher1 | teacher1@example.com | | assistant1 | Terry2 | Teacher2 | teacher2@example.com | | student1 | Sam1 | Student1 | student1@example.com | | student2 | Sam2 | Student2 | student2@example.com | | student3 | Sam3 | Student3 | student3@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | assistant1 | C1 | teacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | And the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | | Group 2 | C1 | G2 | And the following "groupings" exist: | name | course | idnumber | | Grouping 1 | C1 | GG1 | | Grouping 2 | C1 | GG2 | | Grouping 3 | C1 | GG3 | And the following "group members" exist: | user | group | | student1 | G1 | | student2 | G2 | | student3 | G1 | | student3 | G2 | | assistant1 | G1 | And the following "grouping groups" exist: | grouping | group | | GG1 | G1 | | GG2 | G2 | | GG3 | G1 | | GG3 | G2 | Scenario: Check that Added module information is displayed respecting view capability Given the following "activities" exist: | activity | course | name | idnumber | groupmode | grouping | visible | | forum | C1 | ForumVisibleGroups | forum1 | 2 | | 1 | | forum | C1 | ForumSeparateGroups | forum2 | 1 | | 1 | | forum | C1 | ForumHidden | forum3 | 1 | | 0 | | forum | C1 | ForumNoGroups | forum4 | 0 | | 1 | | forum | C1 | ForumVisibleGroupsG1 | forum5 | 2 | GG1 | 1 | | forum | C1 | ForumSeparateGroupsG1 | forum6 | 1 | GG1 | 1 | | forum | C1 | ForumVisibleGroupsG2 | forum7 | 2 | GG2 | 1 | | forum | C1 | ForumSeparateGroupsG2 | forum8 | 1 | GG2 | 1 | And I am on the "ForumVisibleGroupsG1" "forum activity editing" page logged in as teacher1 And I set the following fields to these values: | Access restrictions | Grouping: Grouping 1 | And I press "Save and return to course" And I am on the "ForumSeparateGroupsG1" "forum activity editing" page And I set the following fields to these values: | Access restrictions | Grouping: Grouping 1 | And I press "Save and return to course" And I am on the "ForumVisibleGroupsG2" "forum activity editing" page And I set the following fields to these values: | Access restrictions | Grouping: Grouping 2 | And I press "Save and return to course" And I am on the "ForumSeparateGroupsG2" "forum activity editing" page And I set the following fields to these values: | Access restrictions | Grouping: Grouping 2 | And I press "Save and return to course" And I am on "Course 1" course homepage with editing mode on When I add the "Recent activity" block Then I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I am on the "Course 1" course page logged in as student1 And I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should not see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should not see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should not see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I am on the "Course 1" course page logged in as student2 And I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should not see "ForumHidden" in the "Recent activity" "block" And I should not see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should not see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I am on the "Course 1" course page logged in as student3 And I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should not see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" # Teachers have capability to see all groups and hidden activities And I am on the "Course 1" course page logged in as assistant1 And I should see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" Scenario: Updates and deletes in recent activity block Given the following "activity" exists: | activity | forum | | course | C1 | | idnumber | forum1 | | name | ForumNew | When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Recent activity" block Then I should see "Added Forum" in the "Recent activity" "block" And I should see "ForumNew" in the "Recent activity" "block" And I log out # Update forum as a teacher after a second to ensure we have a new timestamp for recent activity. And I wait "1" seconds And I am on the "Course 1" course page logged in as student1 And I should see "Added Forum" in the "Recent activity" "block" And I should see "ForumNew" in the "Recent activity" "block" And I log out # Update forum as a teacher after a second to ensure we have a new timestamp for recent activity. And I wait "1" seconds # Update forum as a teacher And I am on the "ForumNew" "forum activity editing" page logged in as teacher1 And I set the following fields to these values: | name | ForumUpdated | And I press "Save and return to course" And I log out And I wait "1" seconds # Student 1 already saw that forum was created, now he can see that forum was updated And I am on the "Course 1" course page logged in as student1 And I should not see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should see "Updated Forum" in the "Recent activity" "block" And I should see "ForumUpdated" in the "Recent activity" "block" And I log out And I wait "1" seconds # Student 2 has bigger interval and he can see one entry that forum was created but with the new name And I am on the "Course 1" course page logged in as student2 And I should see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should not see "Updated Forum" in the "Recent activity" "block" And I should see "ForumUpdated" in the "Recent activity" "block" And I log out And I wait "1" seconds # Delete forum as a teacher And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I delete "ForumUpdated" activity And I run all adhoc tasks And I log out And I wait "1" seconds # Students 1 and 2 see that forum was deleted And I am on the "Course 1" course page logged in as student1 And I should not see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should not see "Updated Forum" in the "Recent activity" "block" And I should not see "ForumUpdated" in the "Recent activity" "block" And I should see "Deleted Forum" in the "Recent activity" "block" And I log out And I wait "1" seconds # Student 3 never knew that forum was created, so he does not see anything And I am on the "Course 1" course page logged in as student3 And I should not see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should not see "Updated Forum" in the "Recent activity" "block" And I should not see "ForumUpdated" in the "Recent activity" "block" And I should not see "Deleted Forum" in the "Recent activity" "block" PK'9\0g9..)recent_activity/block_recent_activity.phpnu[. /** * class block_recent_activity * * @package block_recent_activity * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once($CFG->dirroot.'/course/lib.php'); /** * class block_recent_activity * * @package block_recent_activity * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_recent_activity extends block_base { /** * Use {@link block_recent_activity::get_timestart()} to access * * @var int stores the time since when we want to show recent activity */ protected $timestart = null; /** * Initialises the block */ function init() { $this->title = get_string('pluginname', 'block_recent_activity'); } /** * Returns the content object * * @return stdObject */ function get_content() { if ($this->content !== NULL) { return $this->content; } if (empty($this->instance)) { $this->content = ''; return $this->content; } $this->content = new stdClass; $this->content->text = ''; $this->content->footer = ''; $renderer = $this->page->get_renderer('block_recent_activity'); $this->content->text = $renderer->recent_activity($this->page->course, $this->get_timestart(), $this->get_recent_enrolments(), $this->get_structural_changes(), $this->get_modules_recent_activity()); return $this->content; } /** * Returns the time since when we want to show recent activity * * For guest users it is 2 days, for registered users it is the time of last access to the course * * @return int */ protected function get_timestart() { global $USER; if ($this->timestart === null) { $this->timestart = round(time() - COURSE_MAX_RECENT_PERIOD, -2); // better db caching for guests - 100 seconds if (!isguestuser()) { if (!empty($USER->lastcourseaccess[$this->page->course->id])) { if ($USER->lastcourseaccess[$this->page->course->id] > $this->timestart) { $this->timestart = $USER->lastcourseaccess[$this->page->course->id]; } } } } return $this->timestart; } /** * Returns all recent enrolments. * * This function previously used get_recent_enrolments located in lib/deprecatedlib.php which would * return an empty array which was identified in MDL-36993. The use of this function outside the * deprecated lib was removed in MDL-40649. * * @todo MDL-36993 this function always return empty array * @return array array of entries from {user} table */ protected function get_recent_enrolments() { return array(); } /** * Returns list of recent changes in course structure * * It includes adding, editing or deleting of the resources or activities * Excludes changes on modules without a view link (i.e. labels), and also * if activity was both added and deleted * * @return array array of changes. Each element is an array containing attributes: * 'action' - one of: 'add mod', 'update mod', 'delete mod' * 'module' - instance of cm_info (for 'delete mod' it is an object with attributes modname and modfullname) */ protected function get_structural_changes() { global $DB; $course = $this->page->course; $context = context_course::instance($course->id); $canviewdeleted = has_capability('block/recent_activity:viewdeletemodule', $context); $canviewupdated = has_capability('block/recent_activity:viewaddupdatemodule', $context); if (!$canviewdeleted && !$canviewupdated) { return; } $timestart = $this->get_timestart(); $changelist = array(); // The following query will retrieve the latest action for each course module in the specified course. // Also the query filters out the modules that were created and then deleted during the given interval. $sql = "SELECT cmid, MIN(action) AS minaction, MAX(action) AS maxaction, MAX(modname) AS modname FROM {block_recent_activity} WHERE timecreated > ? AND courseid = ? GROUP BY cmid ORDER BY MAX(timecreated) DESC"; $params = array($timestart, $course->id); $logs = $DB->get_records_sql($sql, $params); if (isset($logs[0])) { // If special record for this course and cmid=0 is present, migrate logs. self::migrate_logs($course); $logs = $DB->get_records_sql($sql, $params); } if ($logs) { $modinfo = get_fast_modinfo($course); foreach ($logs as $log) { // We used aggregate functions since constants CM_CREATED, CM_UPDATED and CM_DELETED have ascending order (0,1,2). $wasdeleted = ($log->maxaction == block_recent_activity_observer::CM_DELETED); $wascreated = ($log->minaction == block_recent_activity_observer::CM_CREATED); if ($wasdeleted && $wascreated) { // Activity was created and deleted within this interval. Do not show it. continue; } else if ($wasdeleted && $canviewdeleted) { if (plugin_supports('mod', $log->modname, FEATURE_NO_VIEW_LINK, false)) { // Better to call cm_info::has_view() because it can be dynamic. // But there is no instance of cm_info now. continue; } // Unfortunately we do not know if the mod was visible. $modnames = get_module_types_names(); $changelist[$log->cmid] = array('action' => 'delete mod', 'module' => (object)array( 'modname' => $log->modname, 'modfullname' => isset($modnames[$log->modname]) ? $modnames[$log->modname] : $log->modname )); } else if (!$wasdeleted && isset($modinfo->cms[$log->cmid]) && $canviewupdated) { // Module was either added or updated during this interval and it currently exists. // If module was both added and updated show only "add" action. $cm = $modinfo->cms[$log->cmid]; if ($cm->has_view() && $cm->uservisible) { $changelist[$log->cmid] = array( 'action' => $wascreated ? 'add mod' : 'update mod', 'module' => $cm ); } } } } return $changelist; } /** * Returns list of recent activity within modules * * For each used module type executes callback MODULE_print_recent_activity() * * @return array array of pairs moduletype => content */ protected function get_modules_recent_activity() { $context = context_course::instance($this->page->course->id); $viewfullnames = has_capability('moodle/site:viewfullnames', $context); $hascontent = false; $modinfo = get_fast_modinfo($this->page->course); $usedmodules = $modinfo->get_used_module_names(); $recentactivity = array(); foreach ($usedmodules as $modname => $modfullname) { // Each module gets it's own logs and prints them ob_start(); $hascontent = component_callback('mod_'. $modname, 'print_recent_activity', array($this->page->course, $viewfullnames, $this->get_timestart()), false); if ($hascontent) { $recentactivity[$modname] = ob_get_contents(); } ob_end_clean(); } return $recentactivity; } /** * Which page types this block may appear on. * * @return array page-type prefix => true/false. */ function applicable_formats() { return array('all' => true, 'my' => false, 'tag' => false); } /** * Migrates entries from table {log} into {block_recent_activity} * * We only migrate logs for the courses that actually have recent activity * block and that are being viewed within COURSE_MAX_RECENT_PERIOD time * after the upgrade. * * The presence of entry in {block_recent_activity} with the cmid=0 indicates * that the course needs log migration. Those entries were installed in * db/upgrade.php when the table block_recent_activity was created. * * @param stdClass $course */ protected static function migrate_logs($course) { global $DB; if (!$logstarted = $DB->get_record('block_recent_activity', array('courseid' => $course->id, 'cmid' => 0), 'id, timecreated')) { return; } $DB->delete_records('block_recent_activity', array('id' => $logstarted->id)); try { $logs = $DB->get_records_select('log', "time > ? AND time < ? AND course = ? AND module = 'course' AND (action = 'add mod' OR action = 'update mod' OR action = 'delete mod')", array(time()-COURSE_MAX_RECENT_PERIOD, $logstarted->timecreated, $course->id), 'id ASC', 'id, time, userid, cmid, action, info'); } catch (Exception $e) { // Probably table {log} was already removed. return; } if (!$logs) { return; } $modinfo = get_fast_modinfo($course); $entries = array(); foreach ($logs as $log) { $info = explode(' ', $log->info); if (count($info) != 2) { continue; } $modname = $info[0]; $instanceid = $info[1]; $entry = array('courseid' => $course->id, 'userid' => $log->userid, 'timecreated' => $log->time, 'modname' => $modname); if ($log->action == 'delete mod') { if (!$log->cmid) { continue; } $entry['action'] = 2; $entry['cmid'] = $log->cmid; } else { if (!isset($modinfo->instances[$modname][$instanceid])) { continue; } if ($log->action == 'add mod') { $entry['action'] = 0; } else { $entry['action'] = 1; } $entry['cmid'] = $modinfo->instances[$modname][$instanceid]->id; } $entries[] = $entry; } $DB->insert_records('block_recent_activity', $entries); } } PK'9\&XAZZtests/coverage.phpnu[. /** * Coverage information for the core_block subsystem. * * @copyright 2021 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ return new class extends phpunit_coverage_info { /** @var array The list of files relative to the plugin root to include in coverage generation. */ protected $includelistfiles = [ 'moodleblock.class.php', ]; }; PK'9\CUCUtests/privacy/provider_test.phpnu[. /** * Data provider tests. * * @package core_block * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_block\privacy; defined('MOODLE_INTERNAL') || die(); global $CFG; use core_privacy\tests\provider_testcase; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; use core_block\privacy\provider; /** * Data provider testcase class. * * @package core_block * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { public function setUp(): void { parent::setUp(); $this->resetAfterTest(); } public function test_get_contexts_for_userid(): void { $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $manager = $this->get_block_manager(['region-a'], $c1ctx); $manager->add_block('myprofile', 'region-a', 0, false); $manager->load_blocks(); $blockmyprofile = $manager->get_blocks_for_region('region-a')[0]; $manager = $this->get_block_manager(['region-a'], $c2ctx); $manager->add_block('login', 'region-a', 0, false); $manager->add_block('mentees', 'region-a', 1, false); $manager->load_blocks(); list($blocklogin, $blockmentees) = $manager->get_blocks_for_region('region-a'); $manager = $this->get_block_manager(['region-a'], $u1ctx); $manager->add_block('private_files', 'region-a', 0, false); $manager->load_blocks(); $blockprivatefiles = $manager->get_blocks_for_region('region-a')[0]; $this->set_hidden_pref($blocklogin, true, $u1->id); $this->set_hidden_pref($blockprivatefiles, true, $u1->id); $this->set_docked_pref($blockmyprofile, true, $u1->id); $this->set_docked_pref($blockmentees, true, $u1->id); $this->set_docked_pref($blockmentees, true, $u2->id); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(4, $contextids); $this->assertTrue(in_array($blocklogin->context->id, $contextids)); $this->assertTrue(in_array($blockprivatefiles->context->id, $contextids)); $this->assertTrue(in_array($blockmyprofile->context->id, $contextids)); $this->assertTrue(in_array($blockmentees->context->id, $contextids)); $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); $this->assertCount(1, $contextids); $this->assertTrue(in_array($blockmentees->context->id, $contextids)); } /** * Test that user IDs are returned for a given context. */ public function test_get_users_in_context(): void { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $manager = $this->get_block_manager(['region-a'], \context_course::instance($course->id)); $manager->add_block('myprofile', 'region-a', 0, false); $manager->load_blocks(); $blockmyprofile = $manager->get_blocks_for_region('region-a')[0]; $this->set_hidden_pref($blockmyprofile, true, $u1->id); $this->set_hidden_pref($blockmyprofile, true, $u3->id); $this->set_docked_pref($blockmyprofile, true, $u2->id); $this->set_docked_pref($blockmyprofile, true, $u3->id); $records = $DB->get_records('block_instances', ['blockname' => 'myprofile']); $record = array_shift($records); $blockcontext = \context_block::instance($record->id); $userlist = new \core_privacy\local\request\userlist($blockcontext, 'core_block'); provider::get_users_in_context($userlist); $this->assertCount(3, $userlist->get_userids()); } public function test_delete_data_for_user(): void { global $DB; $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $manager = $this->get_block_manager(['region-a'], $c1ctx); $manager->add_block('myprofile', 'region-a', 0, false); $manager->load_blocks(); $blockmyprofile = $manager->get_blocks_for_region('region-a')[0]; $manager = $this->get_block_manager(['region-a'], $c2ctx); $manager->add_block('login', 'region-a', 0, false); $manager->add_block('mentees', 'region-a', 1, false); $manager->load_blocks(); list($blocklogin, $blockmentees) = $manager->get_blocks_for_region('region-a'); $manager = $this->get_block_manager(['region-a'], $u1ctx); $manager->add_block('private_files', 'region-a', 0, false); $manager->load_blocks(); $blockprivatefiles = $manager->get_blocks_for_region('region-a')[0]; $this->set_hidden_pref($blocklogin, true, $u1->id); $this->set_hidden_pref($blocklogin, true, $u2->id); $this->set_hidden_pref($blockprivatefiles, true, $u1->id); $this->set_hidden_pref($blockmyprofile, true, $u1->id); $this->set_docked_pref($blockmyprofile, true, $u1->id); $this->set_docked_pref($blockmentees, true, $u1->id); $this->set_docked_pref($blockmentees, true, $u2->id); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockprivatefiles->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockmyprofile->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmyprofile->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); provider::delete_data_for_user(new approved_contextlist($u1, 'core_block', [$blocklogin->context->id, $blockmyprofile->context->id, $blockmentees->context->id])); $this->assertFalse($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockprivatefiles->instance->id}hidden"])); $this->assertFalse($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockmyprofile->instance->id}hidden"])); $this->assertFalse($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmyprofile->instance->id}"])); $this->assertFalse($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); } public function test_delete_data_for_all_users_in_context(): void { global $DB; $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $manager = $this->get_block_manager(['region-a'], $c1ctx); $manager->add_block('myprofile', 'region-a', 0, false); $manager->load_blocks(); $blockmyprofile = $manager->get_blocks_for_region('region-a')[0]; $manager = $this->get_block_manager(['region-a'], $c2ctx); $manager->add_block('login', 'region-a', 0, false); $manager->add_block('mentees', 'region-a', 1, false); $manager->load_blocks(); list($blocklogin, $blockmentees) = $manager->get_blocks_for_region('region-a'); $manager = $this->get_block_manager(['region-a'], $u1ctx); $manager->add_block('private_files', 'region-a', 0, false); $manager->load_blocks(); $blockprivatefiles = $manager->get_blocks_for_region('region-a')[0]; $this->set_hidden_pref($blocklogin, true, $u1->id); $this->set_hidden_pref($blocklogin, true, $u2->id); $this->set_hidden_pref($blockprivatefiles, true, $u1->id); $this->set_hidden_pref($blockmyprofile, true, $u1->id); $this->set_docked_pref($blockmyprofile, true, $u1->id); $this->set_docked_pref($blockmentees, true, $u1->id); $this->set_docked_pref($blockmentees, true, $u2->id); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockprivatefiles->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockmyprofile->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmyprofile->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); // Nothing happens. provider::delete_data_for_all_users_in_context($c1ctx); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockprivatefiles->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockmyprofile->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmyprofile->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); // Delete one block. provider::delete_data_for_all_users_in_context($blocklogin->context); $this->assertFalse($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertFalse($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockprivatefiles->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockmyprofile->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmyprofile->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); // Delete another block. provider::delete_data_for_all_users_in_context($blockmyprofile->context); $this->assertFalse($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertFalse($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "block{$blocklogin->instance->id}hidden"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockprivatefiles->instance->id}hidden"])); $this->assertFalse($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "block{$blockmyprofile->instance->id}hidden"])); $this->assertFalse($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmyprofile->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u1->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); $this->assertTrue($DB->record_exists('user_preferences', ['userid' => $u2->id, 'name' => "docked_block_instance_{$blockmentees->instance->id}"])); } /** * Test the deletion of data related to a context and a list of users. */ public function test_delete_data_for_users(): void { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $manager = $this->get_block_manager(['region-a'], \context_course::instance($course->id)); $manager->add_block('myprofile', 'region-a', 0, false); $manager->load_blocks(); $blockmyprofile = $manager->get_blocks_for_region('region-a')[0]; $this->set_hidden_pref($blockmyprofile, true, $u1->id); $this->set_hidden_pref($blockmyprofile, true, $u3->id); $this->set_docked_pref($blockmyprofile, true, $u2->id); $this->set_docked_pref($blockmyprofile, true, $u3->id); $records = $DB->get_records('block_instances', ['blockname' => 'myprofile']); $record = array_shift($records); $blockcontext = \context_block::instance($record->id); $userlist = new \core_privacy\local\request\userlist($blockcontext, 'core_block'); provider::get_users_in_context($userlist); $this->assertCount(3, $userlist->get_userids()); // Delete preferences for user 1 and 3 for the my profile block. $userlist = new \core_privacy\local\request\approved_userlist($blockcontext, 'core_block', [$u1->id, $u3->id]); provider::delete_data_for_users($userlist); // Only user 2's preference is left. $this->assertCount(1, $DB->get_records('user_preferences', ['name' => "docked_block_instance_{$blockcontext->instanceid}"])); // All of these are gone. $this->assertEmpty($DB->get_records('user_preferences', ['name' => "block{$blockcontext->instanceid}hidden"])); } public function test_export_data_for_user(): void { global $DB; $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $yes = transform::yesno(true); $no = transform::yesno(false); $manager = $this->get_block_manager(['region-a'], $c1ctx); $manager->add_block('myprofile', 'region-a', 0, false); $manager->add_block('login', 'region-a', 1, false); $manager->add_block('mentees', 'region-a', 2, false); $manager->add_block('private_files', 'region-a', 3, false); $manager->load_blocks(); list($bmyprofile, $blogin, $bmentees, $bprivatefiles) = $manager->get_blocks_for_region('region-a'); // Set some user preferences. $this->set_hidden_pref($blogin, true, $u1->id); $this->set_docked_pref($blogin, false, $u1->id); $this->set_docked_pref($blogin, true, $u2->id); $this->set_hidden_pref($bprivatefiles, false, $u1->id); $this->set_docked_pref($bprivatefiles, true, $u2->id); $this->set_docked_pref($bmyprofile, true, $u1->id); $this->set_docked_pref($bmentees, true, $u2->id); // Export data. provider::export_user_data(new approved_contextlist($u1, 'core_block', [$bmyprofile->context->id, $blogin->context->id, $bmentees->context->id, $bprivatefiles->context->id])); $prefs = writer::with_context($bmentees->context)->get_user_context_preferences('core_block'); $this->assertEmpty((array) $prefs); $prefs = writer::with_context($blogin->context)->get_user_context_preferences('core_block'); $this->assertEquals($no, $prefs->block_is_docked->value); $this->assertEquals($yes, $prefs->block_is_hidden->value); $prefs = writer::with_context($bprivatefiles->context)->get_user_context_preferences('core_block'); $this->assertObjectNotHasProperty('block_is_docked', $prefs); $this->assertEquals($no, $prefs->block_is_hidden->value); $prefs = writer::with_context($bmyprofile->context)->get_user_context_preferences('core_block'); $this->assertEquals($yes, $prefs->block_is_docked->value); $this->assertObjectNotHasProperty('block_is_hidden', $prefs); } /** * Get the block manager. * * @param array $regions The regions. * @param \context $context The context. * @param string $pagetype The page type. * @param string $subpage The sub page. * @return \block_manager */ protected function get_block_manager($regions, $context, $pagetype = 'page-type', $subpage = '') { $page = new \moodle_page(); $page->set_context($context); $page->set_pagetype($pagetype); $page->set_subpage($subpage); $page->set_url(new \moodle_url('/')); $blockmanager = new \block_manager($page); $blockmanager->add_regions($regions, false); $blockmanager->set_default_region($regions[0]); return $blockmanager; } /** * Set a docked preference. * * @param \block_base $block The block. * @param bool $value The value. * @param int $userid The user ID. */ protected function set_docked_pref($block, $value, $userid) { set_user_preference("docked_block_instance_{$block->instance->id}", $value, $userid); } /** * Set a hidden preference. * * @param \block_base $block The block. * @param bool $value The value. * @param int $userid The user ID. */ protected function set_hidden_pref($block, $value, $userid) { set_user_preference("block{$block->instance->id}hidden", $value, $userid); } } PK'9\"=z{]{]tests/externallib_test.phpnu[. namespace core_block; use core_block_external; use externallib_advanced_testcase; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); require_once($CFG->dirroot . '/my/lib.php'); /** * External block functions unit tests * * @package core_block * @category external * @copyright 2015 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.0 */ final class externallib_test extends externallib_advanced_testcase { /** * Test get_course_blocks */ public function test_get_course_blocks(): void { global $DB, $FULLME; $this->resetAfterTest(true); $this->setAdminUser(); $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $studentrole = $DB->get_record('role', array('shortname' => 'student')); $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id); $page = new \moodle_page(); $page->set_context(\context_course::instance($course->id)); $page->set_pagelayout('course'); $course->format = course_get_format($course)->get_format(); $page->set_pagetype('course-view-' . $course->format); $page->blocks->load_blocks(); $newblock = 'calendar_upcoming'; $page->blocks->add_block_at_end_of_default_region($newblock); $this->setUser($user); // Check for the new block. $result = core_block_external::get_course_blocks($course->id); // We need to execute the return values cleaning process to simulate the web service server. $result = \core_external\external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result); // Expect the new block. $this->assertCount(1, $result['blocks']); $this->assertEquals($newblock, $result['blocks'][0]['name']); } /** * Test get_course_blocks on site home */ public function test_get_course_blocks_site_home(): void { global $DB, $FULLME; $this->resetAfterTest(true); $this->setAdminUser(); $user = $this->getDataGenerator()->create_user(); $page = new \moodle_page(); $page->set_context(\context_course::instance(SITEID)); $page->set_pagelayout('frontpage'); $page->set_pagetype('site-index'); $page->blocks->load_blocks(); $newblock = 'calendar_upcoming'; $page->blocks->add_block_at_end_of_default_region($newblock); $this->setUser($user); // Check for the new block. $result = core_block_external::get_course_blocks(SITEID); // We need to execute the return values cleaning process to simulate the web service server. $result = \core_external\external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result); // Expect the new block. $this->assertCount(1, $result['blocks']); $this->assertEquals($newblock, $result['blocks'][0]['name']); } /** * Test get_course_blocks */ public function test_get_course_blocks_overrides(): void { global $DB, $CFG, $FULLME; $this->resetAfterTest(true); $CFG->defaultblocks_override = 'search_forums,course_list:calendar_upcoming,recent_activity'; $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $studentrole = $DB->get_record('role', array('shortname' => 'student')); $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id); $this->setUser($user); // Try default blocks. $result = core_block_external::get_course_blocks($course->id); // We need to execute the return values cleaning process to simulate the web service server. $result = \core_external\external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result); // Expect 4 default blocks. $this->assertCount(4, $result['blocks']); $expectedblocks = array('navigation', 'settings', 'search_forums', 'course_list', 'calendar_upcoming', 'recent_activity'); foreach ($result['blocks'] as $block) { if (!in_array($block['name'], $expectedblocks)) { $this->fail("Unexpected block found: " . $block['name']); } } } /** * Test get_course_blocks contents */ public function test_get_course_blocks_contents(): void { global $DB, $FULLME; $this->resetAfterTest(true); $this->setAdminUser(); $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $studentrole = $DB->get_record('role', array('shortname' => 'student')); $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id); $coursecontext = \context_course::instance($course->id); // Create a HTML block. $title = 'Some course info'; $body = 'Some course info

                Some contents

                '; $bodyformat = FORMAT_MOODLE; $page = new \moodle_page(); $page->set_context($coursecontext); $page->set_pagelayout('course'); $course->format = course_get_format($course)->get_format(); $page->set_pagetype('course-view-' . $course->format); $page->blocks->load_blocks(); $newblock = 'html'; $page->blocks->add_block_at_end_of_default_region($newblock); $this->setUser($user); // Re-create the page. $page = new \moodle_page(); $page->set_context($coursecontext); $page->set_pagelayout('course'); $course->format = course_get_format($course)->get_format(); $page->set_pagetype('course-view-' . $course->format); $page->blocks->load_blocks(); $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region()); $block = end($blocks); $block = block_instance('html', $block->instance); $nonscalar = [ 'something' => true, ]; $configdata = (object) [ 'title' => $title, 'text' => [ 'itemid' => 0, 'text' => $body, 'format' => $bodyformat, ], 'nonscalar' => $nonscalar ]; $block->instance_config_save((object) $configdata); $filename = 'img.png'; $filerecord = array( 'contextid' => \context_block::instance($block->instance->id)->id, 'component' => 'block_html', 'filearea' => 'content', 'itemid' => 0, 'filepath' => '/', 'filename' => $filename, ); // Create an area to upload the file. $fs = get_file_storage(); // Create a file from the string that we made earlier. $file = $fs->create_file_from_string($filerecord, 'some fake content (should be an image).'); // Check for the new block. $result = core_block_external::get_course_blocks($course->id, true); // We need to execute the return values cleaning process to simulate the web service server. $result = \core_external\external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result); // Expect the new block. $this->assertCount(1, $result['blocks']); $this->assertEquals($title, $result['blocks'][0]['contents']['title']); $this->assertEquals($body, $result['blocks'][0]['contents']['content']); $this->assertEquals(FORMAT_HTML, $result['blocks'][0]['contents']['contentformat']); // Format change for external. $this->assertEquals('', $result['blocks'][0]['contents']['footer']); $this->assertCount(1, $result['blocks'][0]['contents']['files']); $this->assertEquals($newblock, $result['blocks'][0]['name']); $configcounts = 0; foreach ($result['blocks'][0]['configs'] as $config) { if ($config['type'] = 'plugin' && $config['name'] == 'allowcssclasses' && $config['value'] == json_encode('0')) { $configcounts++; } else if ($config['type'] = 'instance' && $config['name'] == 'text' && $config['value'] == json_encode($body)) { $configcounts++; } else if ($config['type'] = 'instance' && $config['name'] == 'title' && $config['value'] == json_encode($title)) { $configcounts++; } else if ($config['type'] = 'instance' && $config['name'] == 'format' && $config['value'] == json_encode('0')) { $configcounts++; } else if ($config['type'] = 'instance' && $config['name'] == 'nonscalar' && $config['value'] == json_encode($nonscalar)) { $configcounts++; } } $this->assertEquals(5, $configcounts); } /** * Test get_course_blocks contents with mathjax. */ public function test_get_course_blocks_contents_with_mathjax(): void { global $DB, $CFG; $this->resetAfterTest(true); $this->setAdminUser(); // Enable MathJax filter in content and headings. $this->configure_filters([ ['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true], ]); // Create a few stuff to test with. $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $studentrole = $DB->get_record('role', array('shortname' => 'student')); $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id); $coursecontext = \context_course::instance($course->id); // Create a HTML block. $title = 'My block $$(a+b)=2$$'; $body = 'My block contents $$(a+b)=2$$'; $bodyformat = FORMAT_MOODLE; $page = new \moodle_page(); $page->set_context($coursecontext); $page->set_pagelayout('course'); $course->format = course_get_format($course)->get_format(); $page->set_pagetype('course-view-' . $course->format); $page->blocks->load_blocks(); $newblock = 'html'; $page->blocks->add_block_at_end_of_default_region($newblock); $this->setUser($user); // Re-create the page. $page = new \moodle_page(); $page->set_context($coursecontext); $page->set_pagelayout('course'); $course->format = course_get_format($course)->get_format(); $page->set_pagetype('course-view-' . $course->format); $page->blocks->load_blocks(); $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region()); $block = end($blocks); $block = block_instance('html', $block->instance); $nonscalar = [ 'something' => true, ]; $configdata = (object) [ 'title' => $title, 'text' => [ 'itemid' => 0, 'text' => $body, 'format' => $bodyformat, ], 'nonscalar' => $nonscalar ]; $block->instance_config_save((object) $configdata); // Check for the new block. $result = core_block_external::get_course_blocks($course->id, true); $result = \core_external\external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result); // Format the original data. $sitecontext = \context_system::instance(); $title = \core_external\util::format_string($title, $coursecontext->id); list($body, $bodyformat) = \core_external\util::format_text($body, $bodyformat, $coursecontext, 'block_html', 'content'); // Check that the block data is formatted. $this->assertCount(1, $result['blocks']); $this->assertStringContainsString('', $result['blocks'][0]['contents']['title']); $this->assertStringContainsString('', $result['blocks'][0]['contents']['content']); $this->assertEquals($title, $result['blocks'][0]['contents']['title']); $this->assertEquals($body, $result['blocks'][0]['contents']['content']); } /** * Test user get default dashboard blocks. */ public function test_get_dashboard_blocks_default_dashboard(): void { global $PAGE, $DB; $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. // Force a setting change to check the returned blocks settings. set_config('displaycategories', 0, 'block_myoverview'); $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true)); // Get the expected default blocks. $alldefaultblocksordered = $DB->get_records_menu( 'block_instances', array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id), 'defaultregion, defaultweight ASC', 'id, blockname' ); $this->setUser($user); // Check for the default blocks. $result = core_block_external::get_dashboard_blocks($user->id); // We need to execute the return values cleaning process to simulate the web service server. $result = \core_external\external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result); // Expect all default blocks defined in blocks_add_default_system_blocks(). $this->assertCount(count($alldefaultblocksordered), $result['blocks']); $returnedblocks = array(); foreach ($result['blocks'] as $block) { // Check all the returned blocks are in the expected blocks array. $this->assertContains($block['name'], $alldefaultblocksordered); $returnedblocks[] = $block['name']; // Check the configuration returned for this default block. if ($block['name'] == 'myoverview') { // Convert config to associative array to avoid DB sorting randomness. $config = array_column($block['configs'], null, 'name'); $this->assertArrayHasKey('displaycategories', $config); $this->assertEquals(json_encode('0'), $config['displaycategories']['value']); $this->assertEquals('plugin', $config['displaycategories']['type']); } } // Check that we received the blocks in the expected order. $this->assertEquals(array_values($alldefaultblocksordered), $returnedblocks); } /** * Test user get default dashboard blocks including a sticky block. */ public function test_get_dashboard_blocks_default_dashboard_including_sticky_block(): void { global $PAGE, $DB; $this->resetAfterTest(true); $this->setAdminUser(); $user = $this->getDataGenerator()->create_user(); $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true)); // Get the expected default blocks. $alldefaultblocks = $DB->get_records_menu( 'block_instances', array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id), '', 'id, blockname' ); // Now, add a sticky block. $page = new \moodle_page(); $page->set_context(\context_system::instance()); $page->set_pagetype('my-index'); $page->set_url(new \moodle_url('/')); $page->blocks->add_region('side-pre'); $page->blocks->load_blocks(); $page->blocks->add_block('myprofile', 'side-pre', 0, true, '*'); $this->setUser($user); // Check for the default blocks plus the sticky. $result = core_block_external::get_dashboard_blocks($user->id); // We need to execute the return values cleaning process to simulate the web service server. $result = \core_external\external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result); // Expect all default blocks defined in blocks_add_default_system_blocks() plus sticky one. $this->assertCount(count($alldefaultblocks) + 1, $result['blocks']); $found = false; foreach ($result['blocks'] as $block) { if ($block['name'] == 'myprofile') { $this->assertEquals('side-pre', $block['region']); $found = true; continue; } // Check that the block is in the expected blocks array. $this->assertContains($block['name'], $alldefaultblocks); } $this->assertTrue($found); } /** * Test admin get user's custom dashboard blocks. */ public function test_get_dashboard_blocks_custom_user_dashboard(): void { global $PAGE, $DB; $this->resetAfterTest(true); $this->setAdminUser(); $user = $this->getDataGenerator()->create_user(); $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true)); // Get the expected default blocks. $alldefaultblocks = $DB->get_records_menu( 'block_instances', array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id), '', 'id, blockname' ); // Add a custom block. $page = new \moodle_page(); $page->set_context(\context_user::instance($user->id)); $page->set_pagelayout('mydashboard'); $page->set_pagetype('my-index'); $page->blocks->add_region('content'); $currentpage = my_get_page($user->id, MY_PAGE_PRIVATE); $page->set_subpage($currentpage->id); $page->blocks->load_blocks(); $page->blocks->add_block('myprofile', 'content', 0, false); $this->setAdminUser(); // Check for the new block as admin for a user. $result = core_block_external::get_dashboard_blocks($user->id); // We need to execute the return values cleaning process to simulate the web service server. $result = \core_external\external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result); // Expect all default blocks defined in blocks_add_default_system_blocks() plus the one we added. $this->assertCount(count($alldefaultblocks) + 1, $result['blocks']); $found = false; foreach ($result['blocks'] as $block) { if ($block['name'] == 'myprofile') { $this->assertEquals('content', $block['region']); $found = true; continue; } // Check that the block is in the expected blocks array. $this->assertContains($block['name'], $alldefaultblocks); } $this->assertTrue($found); } /** * Test user tries to get other user blocks not having permission. */ public function test_get_dashboard_blocks_other_user_missing_permissions(): void { $this->resetAfterTest(true); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $this->setUser($user1); $this->expectException('moodle_exception'); core_block_external::get_dashboard_blocks($user2->id); } /** * Test user get default dashboard blocks for my courses page. */ public function test_get_dashboard_blocks_my_courses(): void { global $PAGE, $DB; $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. // Force a setting change to check the returned blocks settings. set_config('displaycategories', 0, 'block_myoverview'); $systempage = $DB->get_record('my_pages', ['userid' => null, 'name' => MY_PAGE_COURSES, 'private' => false]); // Get the expected default blocks. $alldefaultblocksordered = $DB->get_records_menu( 'block_instances', ['pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id], 'defaultregion, defaultweight ASC', 'id, blockname' ); $this->setUser($user); // Check for the default blocks. $result = core_block_external::get_dashboard_blocks($user->id, false, MY_PAGE_COURSES); // We need to execute the return values cleaning process to simulate the web service server. $result = \core_external\external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result); // Expect all default blocks defined in blocks_add_default_system_blocks(). $this->assertCount(count($alldefaultblocksordered), $result['blocks']); $returnedblocks = []; foreach ($result['blocks'] as $block) { // Check all the returned blocks are in the expected blocks array. $this->assertContains($block['name'], $alldefaultblocksordered); $returnedblocks[] = $block['name']; // Check the configuration returned for this default block. if ($block['name'] == 'myoverview') { // Convert config to associative array to avoid DB sorting randomness. $config = array_column($block['configs'], null, 'name'); $this->assertArrayHasKey('displaycategories', $config); $this->assertEquals(json_encode('0'), $config['displaycategories']['value']); $this->assertEquals('plugin', $config['displaycategories']['type']); } } // Check that we received the blocks in the expected order. $this->assertEquals(array_values($alldefaultblocksordered), $returnedblocks); } /** * Test user passing the wrong page type and getting an exception. */ public function test_get_dashboard_blocks_incorrect_page(): void { global $PAGE; $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. $this->setUser($user); $this->expectException('moodle_exception'); // Check for the default blocks with a fake page, no need to assign as it'll throw. core_block_external::get_dashboard_blocks($user->id, false, 'fakepage'); } } PK'9\Ħ2tests/behat/add_blocks_overridden_disabled.featurenu[@block @core_block @javascript Feature: Add a block when main feature is disabled In order to add a block to my course As a teacher Some blocks should be only added to courses if the main feature they are based on is enabled. Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And I am on the "C1" "course" page logged in as "admin" Scenario Outline: The block is displayed even when main feature is disabled Given the following config values are set as admin: | | 1 | | And I turn editing mode on And I add the "" block When the following config values are set as admin: | | 0 | | Then I should see "" Examples: | blockname | settingname1 | settingplugin1 | | Accessibility review | enableaccessibilitytools | | | Blog menu | enableblogs | | | Recent blog entries | enableblogs | | | Comments | usecomments | | | Course completion status | enablecompletion | | | Global search | enableglobalsearch | | | Latest badges | enablebadges | | | Tags | usetags | | | Learning plans | enabled | core_competency | Scenario Outline: The block is displayed even when main feature is disabled (2 settings) Given the following config values are set as admin: | | 1 | | | 1 | And I turn editing mode on And I add the "" block When the following config values are set as admin: | | 0 | | | 0 | Then I should see "" Examples: | blockname | settingname1 | settingname2 | | Blog tags | enableblogs | usetags | Scenario Outline: The block can be removed even when main feature is disabled Given the following config values are set as admin: | | 1 | | And I turn editing mode on And I add the "" block And I open the "" blocks action menu And I click on "Delete block" "link" in the "" "block" And "Delete block?" "dialogue" should exist And I click on "Cancel" "button" in the "Delete block?" "dialogue" And I should see "" When the following config values are set as admin: | | 0 | | And I open the "" blocks action menu And I click on "Delete block" "link" in the "" "block" And "Delete block?" "dialogue" should exist And I click on "Delete" "button" in the "Delete block?" "dialogue" Then I should not see "" Examples: | blockname | settingname1 | settingplugin1 | | Accessibility review | enableaccessibilitytools | | | Blog menu | enableblogs | | | Recent blog entries | enableblogs | | | Comments | usecomments | | | Course completion status | enablecompletion | | | Global search | enableglobalsearch | | | Latest badges | enablebadges | | | Tags | usetags | | | Learning plans | enabled | core_competency | Scenario Outline: The block can be removed even when main feature is disabled (2 settings) Given the following config values are set as admin: | | 1 | | | 1 | And I turn editing mode on And I add the "" block And I open the "" blocks action menu And I click on "Delete block" "link" in the "" "block" And "Delete block?" "dialogue" should exist And I click on "Cancel" "button" in the "Delete block?" "dialogue" And I should see "" When the following config values are set as admin: | | 0 | | | 0 | And I open the "" blocks action menu And I click on "Delete block" "link" in the "" "block" And "Delete block?" "dialogue" should exist And I click on "Delete" "button" in the "Delete block?" "dialogue" Then I should not see "" Examples: | blockname | settingname1 | settingname2 | | Blog tags | enableblogs | usetags | PK'9\]q  3tests/behat/configure_block_throughout_site.featurenu[@core @core_block Feature: Add and configure blocks throughout the site In order to maintain some patterns across all the site As a manager I need to set and configure blocks throughout the site Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | manager1 | Manager | 1 | manager1@example.com | | teacher1 | teacher | 1 | teacher@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "system role assigns" exist: | user | course | role | | manager1 | Acceptance test site | manager | # Allow at least one role assignment in the block context: And I log in as "admin" And I navigate to "Users > Permissions > Define roles" in site administration And I follow "Edit Non-editing teacher role" And I set the following fields to these values: | Block | 1 | And I press "Save changes" And I log out Scenario: Add and configure a block throughtout the site Given I log in as "manager1" And I am on site homepage And I turn editing mode on And I add the "Comments" block And I configure the "Comments" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" When I am on "Course 1" course homepage Then I should see "Comments" in the "Comments" "block" And I should see "Save comment" in the "Comments" "block" And I am on site homepage And I configure the "Comments" block And I set the following fields to these values: | Default weight | -10 (first) | And I press "Save changes" And I am on "Course 1" course homepage # The first block matching the pattern should be top-left block And I should see "Comments" in the "//*[@id='region-pre' or @id='block-region-side-pre']/descendant::*[contains(concat(' ', normalize-space(@class), ' '), ' block_comments ')]" "xpath_element" Scenario: Blocks on the dashboard page can have roles assigned to them Given I log in as "manager1" When I turn editing mode on Then I should see "Assign roles in Recently accessed items block" Scenario: Blocks on courses can have roles assigned to them Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Search forums" block Then I should see "Assign roles in Search forums block" @javascript Scenario: Blocks can safely be customised Given I log in as "admin" And I am on homepage And I turn editing mode on And I add the "Text" block to the default region with: | Text block title | Foo " onload="document.getElementsByTagName('body')[0].remove()" alt=" | | Content | Example | Then I should see "Example" in the "block_html" "block" Then I should see "document.getElementsByTagName" PK'9\!/tests/behat/return_block_original_state.featurenu[@core @core_block Feature: The context of a block can always be returned to it's original state. In order to revert actions when configuring blocks As an admin I need to be able to return the block to original state Scenario: Add and configure a block to display on every page and revert back Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "activities" exist: | activity | name | intro | course | section | idnumber | | assign | Assignment1 | Description | C1 | 1 | assign1 | | assign | Assignment2 | Description | C1 | 1 | assign1 | And I log in as "admin" When I am on "Course 1" course homepage with editing mode on And I add the "Tags" block Then I should see "Tags" in the "Tags" "block" And I navigate to course participants And I configure the "Tags" block And I set the following fields to these values: | Display on page types | Any page | And I press "Save changes" And I am on "Course 1" course homepage And I follow "Assignment1" And I configure the "Tags" block And I set the following fields to these values: | Display on page types | Any assignment module page | And I press "Save changes" And I should see "Tags" in the "Tags" "block" And I am on "Course 1" course homepage And "Tags" "block" should not exist And I navigate to course participants And "Tags" "block" should not exist And I am on "Course 1" course homepage And I follow "Assignment2" And I should see "Tags" in the "Tags" "block" And I configure the "Tags" block And I set the following fields to these values: | Display on page types | Any page | And I press "Save changes" And I am on "Course 1" course homepage And I should see "Tags" in the "Tags" "block" And I navigate to course participants And I should see "Tags" in the "Tags" "block" PK'9\v tests/behat/delete_block.featurenu[@core @core_block Feature: Block removal via modal In order to remove blocks As a teacher I need to use a modal to confirm the block to delete Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | showinsubcontexts | defaultregion | | search_forums | Course | C1 | course-view-* | 0 | side-pre | | myprofile | System | | * | 1 | side-pre | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on @javascript Scenario: Removing a block via modal should remove the block on the page Given I open the "Search forums" blocks action menu When I click on "Delete Search forums block" "link" in the "Search forums" "block" And I should see "This will delete the block Search forums." in the "Delete block?" "dialogue" And I click on "Delete" "button" in the "Delete block?" "dialogue" And I wait to be redirected Then "Search forums" "block" should not exist And I open the "Logged in user" blocks action menu And I click on "Delete Logged in user block" "link" in the "Logged in user" "block" And I should see "You are about to delete a block that appears elsewhere" in the "Delete block?" "dialogue" And I should see "Original block location: System" in the "Delete block?" "dialogue" And I should see "Display on page types: Any page" in the "Delete block?" "dialogue" And I should see "Are you sure you want to continue?" in the "Delete block?" "dialogue" And I click on "Delete" "button" in the "Delete block?" "dialogue" And I wait to be redirected And "Logged in user" "block" should not exist @javascript Scenario: Cancel removing a block via modal should retain the block on the page Given I open the "Search forums" blocks action menu When I click on "Delete Search forums block" "link" in the "Search forums" "block" And I click on "Cancel" "button" in the "Delete block?" "dialogue" And I should not see "Delete block?" Then "Search forums" "block" should exist PK'9\^Ytests/behat/add_blocks.featurenu[@core @core_block Feature: Add blocks In order to add more functionality to pages As a teacher I need to add blocks to pages Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student2 | C1 | student | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on When I add the "Blog menu" block Then I should see "View my entries about this course" @javascript Scenario: Add a block to a course with Javascript enabled Scenario: Add a block to a course with Javascript disabled PK'9\ft tests/behat/behat_blocks.phpnu[. /** * Steps definitions related with blocks. * * @package core_block * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; use Behat\Gherkin\Node\TableNode as TableNode; require_once(__DIR__ . '/../../../lib/behat/behat_base.php'); /** * Blocks management steps definitions. * * @package core_block * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_blocks extends behat_base { /** * Adds the selected block. Editing mode must be previously enabled. * * @Given /^I add the "(?P(?:[^"]|\\")*)" block$/ * @param string $blockname */ public function i_add_the_block($blockname) { $addblock = get_string('addblock'); $this->execute('behat_general::i_click_on_in_the', [$addblock, 'link_exact', '.add_block_button', 'css_element']); if (!$this->running_javascript()) { $this->execute('behat_general::i_click_on_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']); } else { $this->execute('behat_general::i_click_on_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']); } } /** * Adds the selected block to the specified region * * Editing mode must be previously enabled. * * @Given /^I add the "(?P(?:[^"]|\\")*)" block to the "(?P(?:[^"]|\\")*)" region$/ * @param string $blockname * @param string $region */ public function i_add_the_block_to_the_region(string $blockname, string $region) { if (!$this->running_javascript()) { throw new coding_exception('Adding block to specific region is not possible with Javascript disabled'); } if ($region === "default") { $region = ""; } $csselement = 'a[data-key="addblock"][data-blockregion='.behat_context_helper::escape($region).']'; $addblock = get_string('addblock'); $this->execute('behat_general::i_click_on', [$csselement, 'css_element']); $this->execute('behat_general::i_click_on_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']); } /** * Adds the selected block to the specified region and fills configuration form. * * Editing mode must be previously enabled. * * @Given /^I add the "(?P(?:[^"]|\\")*)" block to the (?P(?:[^"]|\\")*) region with:$/ * @param string $blockname * @param string $region * @param TableNode $data */ public function i_add_the_block_to_the_region_with(string $blockname, string $region, TableNode $data) { $blocklabel = get_string('textellipsis', 'moodle', $blockname); $this->execute('behat_blocks::i_add_the_block_to_the_region', [$blocklabel, $region]); $this->wait_for_pending_js(); $dialogname = get_string('addblock', 'core_block', $blockname); $this->execute('behat_forms::i_set_the_following_fields_in_container_to_these_values', [$dialogname, "dialogue", $data]); $this->execute('behat_general::i_click_on_in_the', ["Save changes", 'button', $dialogname, 'dialogue']); } /** * Adds the selected block if it is not already present. Editing mode must be previously enabled. * * @Given /^I add the "(?P(?:[^"]|\\")*)" block if not present$/ * @param string $blockname */ public function i_add_the_block_if_not_present($blockname) { try { $this->get_text_selector_node('block', $blockname); } catch (ElementNotFoundException $e) { $this->execute('behat_blocks::i_add_the_block', [$blockname]); } } /** * Opens a block's actions menu if it is not already opened. * * @Given /^I open the "(?P(?:[^"]|\\")*)" blocks action menu$/ * @throws DriverException The step is not available when Javascript is disabled * @param string $blockname */ public function i_open_the_blocks_action_menu($blockname) { if (!$this->running_javascript()) { // Action menu does not need to be open if Javascript is off. return; } // If it is already opened we do nothing. $blocknode = $this->get_text_selector_node('block', $blockname); if ($blocknode->hasClass('action-menu-shown')) { return; } $this->execute('behat_general::i_click_on_in_the', ["a[data-bs-toggle='dropdown']", "css_element", $this->escape($blockname), "block"] ); } /** * Clicks on Configure block for specified block. Page must be in editing mode. * * Argument block_name may be either the name of the block or CSS class of the block. * * @Given /^I configure the "(?P(?:[^"]|\\")*)" block$/ * @param string $blockname */ public function i_configure_the_block($blockname) { // Note that since $blockname may be either block name or CSS class, we can not use the exact label of "Configure" link. $this->execute("behat_blocks::i_open_the_blocks_action_menu", $this->escape($blockname)); $this->execute('behat_general::i_click_on_in_the', array("Configure", "link", $this->escape($blockname), "block") ); } /** * Ensures that block can be added to the page but does not actually add it. * * @Then /^the add block selector should contain "(?P(?:[^"]|\\")*)" block$/ * @param string $blockname */ public function the_add_block_selector_should_contain_block($blockname) { $addblock = get_string('addblock'); $this->execute('behat_general::i_click_on', [$addblock, 'link_exact']); $cancelstr = get_string('cancel'); if (!$this->running_javascript()) { $this->execute('behat_general::should_exist_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']); $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'link_exact', '#region-main', 'css_element']); } else { $this->execute('behat_general::should_exist_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']); $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'button', $addblock, 'dialogue']); } } /** * Ensures that block can not be added to the page. * * @Then /^the add block selector should not contain "(?P(?:[^"]|\\")*)" block$/ * @param string $blockname */ public function the_add_block_selector_should_not_contain_block($blockname) { $addblock = get_string('addblock'); $this->execute('behat_general::i_click_on', [$addblock, 'link_exact']); $cancelstr = get_string('cancel'); if (!$this->running_javascript()) { $this->execute('behat_general::should_not_exist_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']); $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'link_exact', '#region-main', 'css_element']); } else { $this->execute('behat_general::should_not_exist_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']); $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'button', $addblock, 'dialogue']); } } } PK'9\FzX-tests/behat/restrict_available_blocks.featurenu[@core @core_block Feature: Allowed blocks controls In order to prevent the use of some blocks As an admin I need to restrict some blocks to be used in courses Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | Scenario: Blocks can be added with the default permissions Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Course completion status" block And I add the "Activities" block Then I should see "Activities" in the "Activities" "block" And I should see "Course completion status" in the "Course completion status" "block" Scenario: Blocks can not be added when the admin restricts the permissions Given the following "role capability" exists: | role | editingteacher | | block/activity_modules:addinstance | prohibit | And I log in as "admin" And I am on the "Course 1" "permissions" page And I override the system permissions of "Teacher" role with: | block/completionstatus:addinstance | Prohibit | And I log out When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Then the add block selector should not contain "Activities" block And the add block selector should not contain "Course completion status" block PK'9\VI& !tests/behat/manage_blocks.featurenu[@core @core_block Feature: Block appearances In order to configure blocks appearance As a teacher I need to add and modify block configuration for the page Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | teacher | 1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | name | course | idnumber | | assign | Test assign name | C1 | assign1 | | book | Test book name | C1 | book1 | And the following "mod_book > chapter" exists: | book | Test book name | | title | Book title | | content | Book content test test | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | comments | Course | C1 | course-view-* | side-pre | And I am on the "Course 1" course page logged in as teacher1 And I turn editing mode on And I configure the "Comments" block And I set the following fields to these values: | Display on page types | Any page | And I press "Save changes" Scenario: Block settings can be modified so that a block apprears on any page When I click on "Test assign name" "link" in the "region-main" "region" Then I should see "Comments" in the "Comments" "block" And I am on "Course 1" course homepage And I configure the "Comments" block And I set the following fields to these values: | Display on page types | Any course page | And I press "Save changes" And I turn editing mode off And I click on "Test assign name" "link" in the "region-main" "region" And I should not see "Comments" Scenario: Block settings can be modified so that a block can be hidden When I click on "Test book name" "link" in the "region-main" "region" And I configure the "Comments" block And I set the following fields to these values: | Visible | No | And I press "Save changes" And I am on "Course 1" course homepage with editing mode off And I click on "Test book name" "link" in the "region-main" "region" Then I should not see "Comments" PK'9\J#tests/behat/hide_blocks.featurenu[@core @core_block Feature: Block visibility In order to configure blocks visibility As a teacher I need to show and hide blocks on a page Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | search_forums | Course | C1 | course-view-* | side-pre | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on @javascript Scenario: Hiding all blocks on the page should remove the column they're in When I open the "Search forums" blocks action menu And I click on "Configure Search forums block" "link" in the "Search forums" "block" And I set the field "Region" to "Right" And I press "Save changes" And I turn editing mode off Then ".empty-region-side-post" "css_element" should not exist in the "body" "css_element" And I turn editing mode on And I open the "Search forums" blocks action menu And I click on "Hide Search forums block" "link" in the "Search forums" "block" And I turn editing mode off And ".empty-region-side-post" "css_element" should exist in the "body" "css_element" PK'9\Otests/behat/move_blocks.featurenu[@core @core_block Feature: Block region moving In order to configure blocks appearance As a teacher I need to modify block region for the page Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | teacher | 1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | course | name | | assign | C1 | Test assign name | | book | C1 | Test book name | And the following "mod_book > chapter" exists: | book | Test book name | | title | Book title | | content | Book content test test | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | comments | Course | C1 | course-view-* | side-pre | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I configure the "Comments" block And I set the following fields to these values: | Display on page types | Any page | And I press "Save changes" Scenario: Block settings can be modified so that a block can be moved When I follow "Test book name" And I configure the "Comments" block And I set the following fields to these values: | Region | Right | And I press "Save changes" And I should see "Comments" in the "//*[@id='region-post' or @id='block-region-side-post']" "xpath_element" PK'9\ޕ>  )tests/behat/add_blocks_overridden.featurenu[@block @core_block @javascript @addablocklink Feature: Add a block when main feature is enabled In order to add a block to my course As a teacher Some blocks should be only added to courses if the main feature they are based on is enabled. Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And I am on the "C1" "course" page logged in as "admin" Scenario Outline: The block can be added when main feature is enabled Given the following config values are set as admin: | | 1 | | | | 1 | | And I turn editing mode on When I click on "Add a block" "link" Then I should see "" Examples: | blockname | settingname1 | settingname2 | settingplugin1 | | Accessibility review | enableaccessibilitytools | | | | Blog menu | enableblogs | | | | Recent blog entries | enableblogs | | | | Blog tags | enableblogs | usetags | | | Comments | usecomments | | | | Course completion status | enablecompletion | | | | Global search | enableglobalsearch | | | | Latest badges | enablebadges | | | | Tags | usetags | | | | Learning plans | enabled | | core_competency | Scenario Outline: The block cannot be added when main feature is disabled Given the following config values are set as admin: | | 0 | | | | 0 | | And I turn editing mode on When I click on "Add a block" "link" Then I should not see "" Examples: | blockname | settingname1 | settingname2 | settingplugin1 | | Accessibility review | enableaccessibilitytools | | | | Blog menu | enableblogs | | | | Recent blog entries | enableblogs | | | | Blog tags | enableblogs | usetags | | | Comments | usecomments | | | | Course completion status | enablecompletion | | | | Global search | enableglobalsearch | | | | Latest badges | enablebadges | | | | Tags | usetags | | | | Learning plans | enabled | | core_competency | PK'9\ 'tests/behat/hidden_block_region.featurenu[@core @core_block Feature: Show hidden blocks in a docked block region when editing In order to edit blocks in a hidden region As a teacher I need to be able to see the blocks when editing is on Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | admin | C1 | editingteacher | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | search_forums | Course | C1 | course-view-* | side-pre | | news_items | Course | C1 | course-view-* | side-pre | | calendar_upcoming | Course | C1 | course-view-* | side-pre | | recent_activity | Course | C1 | course-view-* | side-pre | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on # Hide all the blocks in the non-default region And I configure the "Search forums" block And I set the following fields to these values: | Visible | No | And I click on "Save changes" "button" And I configure the "Latest announcements" block And I set the following fields to these values: | Visible | No | And I click on "Save changes" "button" And I configure the "Upcoming events" block And I set the following fields to these values: | Visible | No | And I click on "Save changes" "button" And I configure the "Recent activity" block And I set the following fields to these values: | Visible | No | When I click on "Save changes" "button" # Editing is on so they should be visible Then I should see "Search forums" And I should see "Latest announcements" And I should see "Upcoming events" And I should see "Recent activity" And I turn editing mode off # Editing is off, so they should no longer be visible And I should not see "Search forums" And I should not see "Latest announcements" And I should not see "Upcoming events" And I should not see "Recent activity" @javascript Scenario: Check that a region with only hidden blocks is not docked in editing mode (javascript enabled) Scenario: Check that a region with only hidden blocks is not docked in editing mode (javascript disabled) PK'9\ltags/db/access.phpnu[. /** * Tags block caps. * * @package block_tags * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/tags:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/tags:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\haootags/lang/en/block_tags.phpnu[. /** * Strings for component 'block_tags', language 'en', branch 'MOODLE_20_STABLE' * * @package block_tags * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['anycollection'] = 'Any'; $string['anytype'] = 'All'; $string['configtitle'] = 'Tags block title'; $string['disabledtags'] = 'Tags are disabled'; $string['defaultdisplay'] = 'Display tags'; $string['pluginname'] = 'Tags'; $string['recursivecontext'] = 'Include child contexts'; $string['recursivecontext_help'] = 'If unticked, tags of items in the context specified above will be displayed, but not tags of items in lower contexts. For example, course tags may be displayed, but not course activity tags.'; $string['standardonly'] = 'Only standard'; $string['tagcollection'] = 'Tag collection'; $string['tagcollection_help'] = 'Select tag collection to display tags from. If you choose "Any" ' . 'the tags from all collections except for those marked with * will be displayed'; $string['taggeditemscontext'] = 'Tagged items context'; $string['taggeditemscontext_help'] = 'You can limit the tag cloud to the tags that are present in the current course category, course or module'; $string['tags:addinstance'] = 'Add a new tags block'; $string['tags:myaddinstance'] = 'Add a new tags block to Dashboard'; $string['privacy:metadata'] = 'The Tags block only shows data stored in other locations.'; PK'9\q> 5tags/backup/moodle2/restore_tags_block_task.class.phpnu[. /** * @package block_tags * @copyright 2016 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Specialised restore task for the tags block * (using execute_after_tasks for recoding of tag collection id) * * @package block_tags * @copyright 2016 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class restore_tags_block_task extends restore_block_task { protected function define_my_settings() { } protected function define_my_steps() { } public function get_fileareas() { return array(); // No associated fileareas. } public function get_configdata_encoded_attributes() { return array(); // No special handling of configdata. } /** * This function, executed after all the tasks in the plan * have been executed, will remove tag collection reference in case block was restored into another site. * Also get mapping of contextid. */ public function after_restore() { global $DB; // Get the blockid. $blockid = $this->get_blockid(); // Extract block configdata and remove tag collection reference if this is another site. Also map contextid. if ($configdata = $DB->get_field('block_instances', 'configdata', array('id' => $blockid))) { $config = $this->decode_configdata($configdata); $changed = false; if (!empty($config->tagcoll) && $config->tagcoll > 1 && !$this->is_samesite()) { $config->tagcoll = 0; $changed = true; } if (!empty($config->ctx)) { if ($ctxmap = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'context', $config->ctx)) { $config->ctx = $ctxmap->newitemid; } else { $config->ctx = 0; } $changed = true; } if ($changed) { $configdata = base64_encode(serialize($config)); $DB->set_field('block_instances', 'configdata', $configdata, array('id' => $blockid)); } } } public static function define_decode_contents() { return array(); } public static function define_decode_rules() { return array(); } } PK'9\[YYtags/edit_form.phpnu[. /** * Form for editing tag block instances. * * @package block_tags * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Form for editing tag block instances. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_tags_edit_form extends block_edit_form { protected function specific_definition($mform) { global $CFG; // Fields for editing HTML block title and contents. $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); $mform->addElement('text', 'config_title', get_string('configtitle', 'block_tags')); $mform->setType('config_title', PARAM_TEXT); $mform->setDefault('config_title', get_string('pluginname', 'block_tags')); $this->add_collection_selector($mform); $numberoftags = array(); for ($i = 1; $i <= 200; $i++) { $numberoftags[$i] = $i; } $mform->addElement('select', 'config_numberoftags', get_string('numberoftags', 'blog'), $numberoftags); $mform->setDefault('config_numberoftags', 80); $defaults = array( core_tag_tag::STANDARD_ONLY => get_string('standardonly', 'block_tags'), core_tag_tag::BOTH_STANDARD_AND_NOT => get_string('anytype', 'block_tags')); $mform->addElement('select', 'config_showstandard', get_string('defaultdisplay', 'block_tags'), $defaults); $mform->setDefault('config_showstandard', core_tag_tag::BOTH_STANDARD_AND_NOT); $defaults = array(0 => context_system::instance()->get_context_name()); $parentcontext = context::instance_by_id($this->block->instance->parentcontextid); if ($parentcontext->contextlevel > CONTEXT_COURSE) { $coursecontext = $parentcontext->get_course_context(); $defaults[$coursecontext->id] = $coursecontext->get_context_name(); } if ($parentcontext->contextlevel != CONTEXT_SYSTEM) { $defaults[$parentcontext->id] = $parentcontext->get_context_name(); } $mform->addElement('select', 'config_ctx', get_string('taggeditemscontext', 'block_tags'), $defaults); $mform->addHelpButton('config_ctx', 'taggeditemscontext', 'block_tags'); $mform->setDefault('config_ctx', 0); $mform->addElement('advcheckbox', 'config_rec', get_string('recursivecontext', 'block_tags')); $mform->addHelpButton('config_rec', 'recursivecontext', 'block_tags'); $mform->setDefault('config_rec', 1); } /** * Add the tag collection selector * * @param object $mform the form being built. */ protected function add_collection_selector($mform) { $tagcolls = core_tag_collection::get_collections_menu(false, true, get_string('anycollection', 'block_tags')); if (count($tagcolls) <= 1) { $mform->addElement('hidden', 'config_tagcoll', 0); $mform->setType('config_tagcoll', PARAM_INT); return; } $mform->addElement('select', 'config_tagcoll', get_string('tagcollection', 'block_tags'), $tagcolls); $mform->setDefault('config_tagcoll', 0); } } PK'9\5`tags/version.phpnu[. /** * Version details * * @package block_tags * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_tags'; // Full name of the plugin (used for diagnostics) PK'9\IE!tags/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_tags. * * @package block_tags * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_tags\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_tags implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\S >َtags/block_tags.phpnu[. /** * Tags block. * * @package block_tags * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_tags extends block_base { public function init() { $this->title = get_string('pluginname', 'block_tags'); } public function instance_allow_multiple() { return true; } public function applicable_formats() { return array('all' => true); } public function instance_allow_config() { return true; } public function specialization() { // Load userdefined title and make sure it's never empty. if (empty($this->config->title)) { $this->title = get_string('pluginname', 'block_tags'); } else { $this->title = format_string($this->config->title, true, ['context' => $this->context]); } } public function get_content() { global $CFG, $COURSE, $USER, $SCRIPT, $OUTPUT; if (empty($CFG->usetags)) { $this->content = new stdClass(); $this->content->text = ''; if ($this->page->user_is_editing()) { $this->content->text = get_string('disabledtags', 'block_tags'); } return $this->content; } if (!isset($this->config)) { $this->config = new stdClass(); } if (empty($this->config->numberoftags)) { $this->config->numberoftags = 80; } if (empty($this->config->showstandard)) { $this->config->showstandard = core_tag_tag::BOTH_STANDARD_AND_NOT; } if (empty($this->config->ctx)) { $this->config->ctx = 0; } if (empty($this->config->rec)) { $this->config->rec = 1; } if (empty($this->config->tagcoll)) { $this->config->tagcoll = 0; } if ($this->content !== NULL) { return $this->content; } if (empty($this->instance)) { $this->content = ''; return $this->content; } $this->content = new stdClass; $this->content->text = ''; $this->content->footer = ''; // Get a list of tags. $tagcloud = core_tag_collection::get_tag_cloud($this->config->tagcoll, $this->config->showstandard == core_tag_tag::STANDARD_ONLY, $this->config->numberoftags, 'name', '', $this->page->context->id, $this->config->ctx, $this->config->rec); $this->content->text = $OUTPUT->render_from_template('core_tag/tagcloud', $tagcloud->export_for_template($OUTPUT)); return $this->content; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { // Return all settings for all users since it is safe (no private keys, etc..). $configs = !empty($this->config) ? $this->config : new stdClass(); return (object) [ 'instance' => $configs, 'plugin' => new stdClass(), ]; } /** * This block shouldn't be added to a page if the tags advanced feature is disabled. * * @param moodle_page $page * @return bool */ public function can_block_be_added(moodle_page $page): bool { global $CFG; return $CFG->usetags; } } PK'9\ntags/tests/tags_test.phpnu[. namespace block_tags; use advanced_testcase; use block_tags; use context_course; /** * PHPUnit block_tags tests * * @package block_tags * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \block_tags */ final class tags_test extends advanced_testcase { public static function setUpBeforeClass(): void { require_once(__DIR__ . '/../../moodleblock.class.php'); require_once(__DIR__ . '/../block_tags.php'); parent::setUpBeforeClass(); } /** * Test the behaviour of can_block_be_added() method. * * @covers ::can_block_be_added */ public function test_can_block_be_added(): void { $this->resetAfterTest(); $this->setAdminUser(); // Create a course and prepare the page where the block will be added. $course = $this->getDataGenerator()->create_course(); $page = new \moodle_page(); $page->set_context(context_course::instance($course->id)); $page->set_pagelayout('course'); $block = new block_tags(); // If blogs advanced feature is enabled, the method should return true. set_config('usetags', true); $this->assertTrue($block->can_block_be_added($page)); // However, if the blogs advanced feature is disabled, the method should return false. set_config('usetags', false); $this->assertFalse($block->can_block_be_added($page)); } } PK'9\CQ5 !tags/tests/behat/tagcloud.featurenu[@block @block_tags @core_tag Feature: Block tags displaying tag cloud In order to view system tags As a user I need to be able to use the block tags Background: Given the following "users" exist: | username | firstname | lastname | email | interests | | teacher1 | Teacher | 1 | teacher1@example.com | Dogs, Cats | | student1 | Student | 1 | student1@example.com | | And the following "courses" exist: | fullname | shortname | | Course 1 | c1 | And the following "tags" exist: | name | isstandard | | Neverusedtag | 1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | c1 | editingteacher | | student1 | c1 | student | Scenario: Add Tags block on a front page When I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Tags" block And I log out And I am on site homepage Then I should see "Dogs" in the "Tags" "block" And I should see "Cats" in the "Tags" "block" And I should not see "Neverusedtag" in the "Tags" "block" And I click on "Dogs" "link" in the "Tags" "block" And I should see "You are not logged in" Scenario: Add Tags block in a course When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Tags" block And I log out And I log in as "student1" And I am on "Course 1" course homepage Then I should see "Dogs" in the "Tags" "block" And I should see "Cats" in the "Tags" "block" And I should not see "Neverusedtag" in the "Tags" "block" And I click on "Dogs" "link" in the "Tags" "block" And I should see "User interests" in the ".tag-index-items h3" "css_element" And I should see "Teacher 1" And I log out @javascript Scenario: Tag block configuration allows to select from searchable tag collections only When I log in as "student1" And I turn editing mode on And I add the "Tags" block And I configure the "Tags" block Then I should not see "Tag collection" And I press "Save changes" And I log out And I log in as "admin" And I navigate to "Appearance > Manage tags" in site administration And I follow "Add tag collection" And I set the following fields to these values: | Name | Collection1 | | Searchable | 1 | And I press "Create" And I follow "Add tag collection" And I set the following fields to these values: | Name | Collection2 | | Searchable | 0 | And I press "Create" And I log out And I log in as "student1" And I turn editing mode on And I am on homepage And I configure the "Tags" block And the "Tag collection" select box should contain "Collection1" And the "Tag collection" select box should not contain "Collection2" And I press "Save changes" PK'9\[@N11calendar_upcoming/db/access.phpnu[. /** * Calendar upcoming block caps. * * @package block_calendar_upcoming * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/calendar_upcoming:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/calendar_upcoming:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\0a calendar_upcoming/db/upgrade.phpnu[. /** * This file keeps track of upgrades to the calendar_upcoming block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @since Moodle 2.8 * @package block_calendar_upcoming * @copyright 2014 Andrew Davis * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade the calendar_upcoming block * @param int $oldversion * @param object $block */ function xmldb_block_calendar_upcoming_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK'9\+555calendar_upcoming/lang/en/block_calendar_upcoming.phpnu[. /** * Strings for component 'block_calendar_upcoming', language 'en', branch 'MOODLE_20_STABLE' * * @package block_calendar_upcoming * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['calendar_upcoming:addinstance'] = 'Add a new upcoming events block'; $string['calendar_upcoming:myaddinstance'] = 'Add a new upcoming events block to Dashboard'; $string['gotocalendar'] = 'Go to calendar...'; $string['pluginname'] = 'Upcoming events'; $string['privacy:metadata'] = 'The Upcoming events block only displays existing calendar data.'; PK'9\Scalendar_upcoming/version.phpnu[. /** * Version details * * @package block_calendar_upcoming * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_calendar_upcoming'; // Full name of the plugin (used for diagnostics) PK'9\Oe -calendar_upcoming/block_calendar_upcoming.phpnu[. /** * Handles displaying the calendar upcoming events block. * * @package block_calendar_upcoming * @copyright 2004 Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_calendar_upcoming extends block_base { /** * Initialise the block. */ public function init() { $this->title = get_string('pluginname', 'block_calendar_upcoming'); } /** * Return the content of this block. * * @return stdClass the content */ public function get_content() { global $CFG; require_once($CFG->dirroot.'/calendar/lib.php'); if ($this->content !== null) { return $this->content; } $this->content = new stdClass; $this->content->text = ''; $this->content->footer = ''; $courseid = $this->page->course->id; $categoryid = ($this->page->context->contextlevel === CONTEXT_COURSECAT) ? $this->page->category->id : null; $calendar = \calendar_information::create(time(), $courseid, $categoryid); list($data, $template) = calendar_get_view($calendar, 'upcoming_mini'); $renderer = $this->page->get_renderer('core_calendar'); $this->content->text .= $renderer->render_from_template($template, $data); $url = new \moodle_url('/calendar/view.php', ['view' => 'upcoming']); if ($courseid != SITEID) { $url->param('course', $this->page->course->id); } else if (!empty($categoryid)) { $url->param('category', $this->page->category->id); } $this->content->footer = html_writer::div( html_writer::link($url, get_string('gotocalendar', 'block_calendar_upcoming')), 'gotocal' ); return $this->content; } } PK'9\OEg  .calendar_upcoming/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_calendar_upcoming. * * @package block_calendar_upcoming * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_calendar_upcoming\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_calendar_upcoming implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\(,calendar_upcoming/upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === === 4.4 === * The following previously deprecated methods have been removed and can no longer be used: - block_calendar_upcoming::get_upcoming_content === 3.4 === * block_calendar_upcoming::get_upcoming_content has been deprecated. Please update your code to use the new APIs. You can see an example of how these may be used in block_calendar_upcoming::get_content(). PK'9\~)calendar_upcoming/tests/generator/lib.phpnu[. /** * Calendar upcoming events block data generator class. * * @package block_calendar_upcoming * @category test * @copyright 2021 Sara Arjona * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_calendar_upcoming_generator extends testing_block_generator { } PK'9\ uDcalendar_upcoming/tests/behat/block_calendar_upcoming_course.featurenu[@block @block_calendar_upcoming Feature: Enable the upcoming events block in a course In order to enable the calendar block in a course As a teacher I can view the event in the upcoming events block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | calendar_upcoming | System | 1 | my-index | side-post | @javascript Scenario: View a site event in the calendar block Given I log in as "admin" And I create a calendar event with form data: | id_eventtype | Site | | id_name | My Site Event | And I log out When I log in as "teacher1" Then I should see "My Site Event" in the "Upcoming events" "block" PK'9\0âGcalendar_upcoming/tests/behat/block_calendar_upcoming_dashboard.featurenu[@block @block_calendar_upcoming Feature: View a upcoming site event on the dashboard In order to view a site event As a student I can view the event in the upcoming events block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | @javascript Scenario: View a site event in the upcoming events block on the dashboard Given I log in as "admin" And I create a calendar event with form data: | id_eventtype | Site | | id_name | My Site Event | And I log out When I log in as "student1" And I hover over today in the mini-calendar block Then I should see "My Site Event" PK'9\SSGcalendar_upcoming/tests/behat/block_calendar_upcoming_frontpage.featurenu[@block @block_calendar_upcoming Feature: View a site event on the frontpage In order to view a site event As a teacher I can view the event in the upcoming events block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | calendar_upcoming | System | 1 | site-index | side-pre | @javascript Scenario: View a site event in the upcoming events block on the frontpage Given I log in as "admin" And I create a calendar event with form data: | id_eventtype | Site | | id_name | My Site Event | And I log out When I log in as "teacher1" And I am on site homepage Then I should see "My Site Event" in the "Upcoming events" "block" PK'9\ަqqcourse_list/styles.cssnu[.block_course_list .footer { margin-top: 5px; } .block_course_list .content li { margin-bottom: .3em; } PK'9\pf#course_list/db/access.phpnu[. /** * Course list block caps. * * @package block_course_list * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/course_list:myaddinstance' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array( 'user' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/my:manageblocks' ), 'block/course_list:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\B)course_list/lang/en/block_course_list.phpnu[. /** * Strings for component 'block_course_list', language 'en', branch 'MOODLE_20_STABLE' * * @package block_course_list * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['adminview'] = 'Admin view'; $string['allcourses'] = 'Admin user sees all courses'; $string['configadminview'] = 'Whether to display all courses in the Courses block, or only courses that the admin is enrolled in.'; $string['confighideallcourseslink'] = 'Remove the \'All courses\' link under the list of courses. (This setting does not affect the admin view.)'; $string['course_list:addinstance'] = 'Add a new courses block'; $string['course_list:myaddinstance'] = 'Add a new courses block to Dashboard'; $string['hideallcourseslink'] = 'Hide \'All courses\' link'; $string['owncourses'] = 'Admin user sees own courses'; $string['pluginname'] = 'Courses'; $string['privacy:metadata'] = 'The Courses block only shows data about courses and does not store any data itself.'; PK'9\ Bcourse_list/version.phpnu[. /** * Version details * * @package block_course_list * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_course_list'; // Full name of the plugin (used for diagnostics) PK'9\#&(course_list/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_course_list. * * @package block_course_list * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_course_list\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_course_list implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\0S""!course_list/block_course_list.phpnu[. /** * Course list block. * * @package block_course_list * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ include_once($CFG->dirroot . '/course/lib.php'); class block_course_list extends block_list { function init() { $this->title = get_string('pluginname', 'block_course_list'); } function has_config() { return true; } function get_content() { global $CFG, $USER, $DB, $OUTPUT; if($this->content !== NULL) { return $this->content; } $this->content = new stdClass; $this->content->items = array(); $this->content->icons = array(); $this->content->footer = ''; $icon = $OUTPUT->pix_icon('i/course', get_string('course')); $adminseesall = true; if (isset($CFG->block_course_list_adminview)) { if ( $CFG->block_course_list_adminview == 'own'){ $adminseesall = false; } } $allcourselink = (has_capability('moodle/course:update', context_system::instance()) || empty($CFG->block_course_list_hideallcourseslink)) && core_course_category::user_top(); if (empty($CFG->disablemycourses) and isloggedin() and !isguestuser() and !(has_capability('moodle/course:update', context_system::instance()) and $adminseesall)) { // Just print My Courses if ($courses = enrol_get_my_courses()) { foreach ($courses as $course) { $coursecontext = context_course::instance($course->id); $linkcss = $course->visible ? "" : " class=\"dimmed\" "; $this->content->items[]="shortname, true, array('context' => $coursecontext)) . "\" ". "href=\"$CFG->wwwroot/course/view.php?id=$course->id\">".$icon.format_string(get_course_display_name_for_list($course)). ""; } $this->title = get_string('mycourses'); /// If we can update any course of the view all isn't hidden, show the view all courses link if ($allcourselink) { $this->content->footer = "wwwroot/course/index.php\">".get_string("fulllistofcourses")." ..."; } } $this->get_remote_courses(); if ($this->content->items) { // make sure we don't return an empty list return $this->content; } } // User is not enrolled in any courses, show list of available categories or courses (if there is only one category). $topcategory = core_course_category::top(); if ($topcategory->is_uservisible() && ($categories = $topcategory->get_children())) { // Check we have categories. if (count($categories) > 1 || (count($categories) == 1 && $DB->count_records('course') > 200)) { // Just print top level category links foreach ($categories as $category) { $categoryname = $category->get_formatted_name(); $linkcss = $category->visible ? "" : " class=\"dimmed\" "; $this->content->items[]="wwwroot/course/index.php?categoryid=$category->id\">".$icon . $categoryname . ""; } /// If we can update any course of the view all isn't hidden, show the view all courses link if ($allcourselink) { $this->content->footer .= "wwwroot/course/index.php\">".get_string('fulllistofcourses').' ...'; } $this->title = get_string('categories'); } else { // Just print course names of single category $category = array_shift($categories); $courses = $category->get_courses(); if ($courses) { foreach ($courses as $course) { $coursecontext = context_course::instance($course->id); $linkcss = $course->visible ? "" : " class=\"dimmed\" "; $this->content->items[]="get_formatted_shortname())."\" ". "href=\"$CFG->wwwroot/course/view.php?id=$course->id\">" .$icon. $course->get_formatted_name() . ""; } /// If we can update any course of the view all isn't hidden, show the view all courses link if ($allcourselink) { $this->content->footer .= "wwwroot/course/index.php\">".get_string('fulllistofcourses').' ...'; } $this->get_remote_courses(); } else { $this->content->icons[] = ''; $this->content->items[] = get_string('nocoursesyet'); if (has_capability('moodle/course:create', context_coursecat::instance($category->id))) { $this->content->footer = ''.get_string("addnewcourse").' ...'; } $this->get_remote_courses(); } $this->title = get_string('courses'); } } return $this->content; } function get_remote_courses() { global $CFG, $USER, $OUTPUT; if (!is_enabled_auth('mnet')) { // no need to query anything remote related return; } $icon = $OUTPUT->pix_icon('i/mnethost', get_string('host', 'mnet')); // shortcut - the rest is only for logged in users! if (!isloggedin() || isguestuser()) { return false; } if ($courses = get_my_remotecourses()) { $this->content->items[] = get_string('remotecourses','mnet'); $this->content->icons[] = ''; foreach ($courses as $course) { $this->content->items[]="shortname, true) . "\" ". "href=\"{$CFG->wwwroot}/auth/mnet/jump.php?hostid={$course->hostid}&wantsurl=/course/view.php?id={$course->remoteid}\">" .$icon. format_string(get_course_display_name_for_list($course)) . ""; } // if we listed courses, we are done return true; } if ($hosts = get_my_remotehosts()) { $this->content->items[] = get_string('remotehosts', 'mnet'); $this->content->icons[] = ''; foreach($USER->mnet_foreign_host_array as $somehost) { $this->content->items[] = $somehost['count'].get_string('courseson','mnet').''.$icon.$somehost['name'].''; } // if we listed hosts, done return true; } return false; } /** * Returns the role that best describes the course list block. * * @return string */ public function get_aria_role() { return 'navigation'; } /** * Return the plugin config settings for external functions. * * @return stdClass the configs for both the block instance and plugin * @since Moodle 3.8 */ public function get_config_for_external() { global $CFG; // Return all settings for all users since it is safe (no private keys, etc..). $configs = (object) [ 'adminview' => $CFG->block_course_list_adminview, 'hideallcourseslink' => $CFG->block_course_list_hideallcourseslink ]; return (object) [ 'instance' => new stdClass(), 'plugin' => $configs, ]; } } PK'9\X~CC;course_list/tests/behat/block_course_list_frontpage.featurenu[@block @block_course_list Feature: Enable the course_list block on the frontpage and view it's contents In order to enable the course list block on the frontpage As an admin I can add the course list block to the frontpage Background: Given the following "categories" exist: | name | category | idnumber | | Category A | 0 | CATA | | Category B | 0 | CATB | | Category C | CATB | CATC | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | | Course 2 | C2 | CATA | | Course 3 | C3 | CATB | | Course 4 | C4 | CATC | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | First | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | teacher1 | C2 | editingteacher | | teacher1 | C3 | editingteacher | Scenario: Add the course list block on the frontpage and navigate to the course listing Given I log in as "admin" And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Courses" block And I log out When I log in as "teacher1" And I am on site homepage Then I should see "Course 1" in the "My courses" "block" And I should see "Course 2" in the "My courses" "block" And I should see "Course 3" in the "My courses" "block" And I should not see "Course 4" in the "My courses" "block" And I follow "All courses" And I should see "Category 1" Scenario: Add the course list block on the frontpage page and navigate to another course Given I log in as "admin" And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Courses" block And I log out When I log in as "teacher1" And I am on site homepage Then I should see "Course 1" in the "My courses" "block" And I should see "Course 2" in the "My courses" "block" And I should see "Course 3" in the "My courses" "block" And I should not see "Course 4" in the "My courses" "block" And I am on "Course 3" course homepage And I should see "Course 3" Scenario: Add the course list block on the frontpage page and view as an admin Given I log in as "admin" And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| When I add the "Courses" block Then I should see "Category 1" in the "Course categories" "block" And I should see "Category A" in the "Course categories" "block" And I should see "Category B" in the "Course categories" "block" And I should not see "Category C" in the "Course categories" "block" And I should not see "Course 1" in the "Course categories" "block" And I should not see "Course 2" in the "Course categories" "block" And I follow "All courses" And I should see "Category 1" Scenario: Add the course list block on the frontpage page and view as a guest Given I log in as "admin" And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Courses" block And I log out When I log in as "guest" And I am on site homepage Then I should see "Category 1" in the "Course categories" "block" And I should see "Category A" in the "Course categories" "block" And I should see "Category B" in the "Course categories" "block" And I should not see "Category C" in the "Course categories" "block" And I should not see "Course 1" in the "Course categories" "block" And I should not see "Course 2" in the "Course categories" "block" And I follow "All courses" And I should see "Category 1" PK'9\\8course_list/tests/behat/block_course_list_course.featurenu[@block @block_course_list Feature: Enable the course_list block on a course page and view it's contents In order to enable the course list block on an course page As a teacher I can add the course list block to a course page Background: Given the following "categories" exist: | name | category | idnumber | | Category A | 0 | CATA | | Category B | 0 | CATB | | Category C | CATB | CATC | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | | Course 2 | C2 | CATA | | Course 3 | C3 | CATB | | Course 4 | C4 | CATC | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | First | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | teacher1 | C2 | editingteacher | | teacher1 | C3 | editingteacher | Scenario: Add the course list block on course page and navigate to the course listing Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| When I add the "Courses" block Then I should see "Course 1" in the "My courses" "block" And I should see "Course 2" in the "My courses" "block" And I should see "Course 3" in the "My courses" "block" And I should not see "Course 4" in the "My courses" "block" And I follow "All courses" And I should see "Category 1" Scenario: Add the course list block on course page and navigate to another course Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| When I add the "Courses" block Then I should see "Course 1" in the "My courses" "block" And I should see "Course 2" in the "My courses" "block" And I should see "Course 3" in the "My courses" "block" And I should not see "Course 4" in the "My courses" "block" And I am on "Course 3" course homepage And I should see "Course 3" Scenario: Add the course list block on course page and view as an admin Given I log in as "admin" And I am on "Course 1" course homepage with editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| When I add the "Courses" block Then I should see "Category 1" in the "Course categories" "block" And I should see "Category A" in the "Course categories" "block" And I should see "Category B" in the "Course categories" "block" And I should not see "Category C" in the "Course categories" "block" And I should not see "Course 1" in the "Course categories" "block" And I should not see "Course 2" in the "Course categories" "block" And I follow "All courses" And I should see "Category 1" Scenario: View the course list block on course page with hide all courses link enabled Given the following config values are set as admin: | block_course_list_hideallcourseslink | 1 | | | unaddableblocks | | theme_boost| And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Courses" block Then I should not see "All courses" in the "My courses" "block" Scenario: View the course list block on course page with admin sees own course enabled Given the following config values are set as admin: | block_course_list_adminview | own | | | unaddableblocks | | theme_boost| And the following "course enrolments" exist: | user | course | role | | admin | C1 | editingteacher | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on When I add the "Courses" block Then I should not see "Category 1" in the "My courses" "block" And I should not see "Category A" in the "My courses" "block" And I should not see "Category B" in the "My courses" "block" And I should not see "Category C" in the "My courses" "block" And I should see "Course 1" in the "My courses" "block" And I should not see "Course 2" in the "My courses" "block" And I follow "All courses" And I should see "Category 1" PK'9\Ņ ;course_list/tests/behat/block_course_list_dashboard.featurenu[@block @block_course_list Feature: Enable the course_list block on the dashboard and view it's contents In order to enable the course list block on the dashboard As a user I can add the course list block to the dashboard Background: Given the following "categories" exist: | name | category | idnumber | | Category A | 0 | CATA | | Category B | 0 | CATB | | Category C | CATB | CATC | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | | Course 2 | C2 | CATA | | Course 3 | C3 | CATB | | Course 4 | C4 | CATC | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | First | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | teacher1 | C2 | editingteacher | | teacher1 | C3 | editingteacher | Scenario: Add the course list block on the dashboard and navigate to the course listing Given I log in as "teacher1" And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| When I add the "Courses" block Then I should see "Course 1" in the "My courses" "block" And I should see "Course 2" in the "My courses" "block" And I should see "Course 3" in the "My courses" "block" And I should not see "Course 4" in the "My courses" "block" And I follow "All courses" And I should see "Category 1" Scenario: Add the course list block on the dashboard and navigate to another course Given I log in as "teacher1" And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| When I add the "Courses" block Then I should see "Course 1" in the "My courses" "block" And I should see "Course 2" in the "My courses" "block" And I should see "Course 3" in the "My courses" "block" And I should not see "Course 4" in the "My courses" "block" And I am on "Course 3" course homepage And I should see "Course 3" Scenario: Add the course list block on the dashboard and view as an admin Given I log in as "admin" And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| When I add the "Courses" block Then I should see "Category 1" in the "Course categories" "block" And I should see "Category A" in the "Course categories" "block" And I should see "Category B" in the "Course categories" "block" And I should not see "Category C" in the "Course categories" "block" And I should not see "Course 1" in the "Course categories" "block" And I should not see "Course 2" in the "Course categories" "block" And I follow "All courses" And I should see "Category 1" PK'9\ 9 :course_list/tests/behat/block_course_list_category.featurenu[@block @block_course_list Feature: Enable the course_list block on a category page and view it's contents In order to enable the course list block on a category page As an admin I can add the course list block to a category page Background: Given the following "categories" exist: | name | category | idnumber | | Category A | 0 | CATA | | Category B | 0 | CATB | | Category C | CATB | CATC | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | | Course 2 | C2 | CATA | | Course 3 | C3 | CATB | | Course 4 | C4 | CATC | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | First | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | teacher1 | C2 | editingteacher | | teacher1 | C3 | editingteacher | Scenario: Add the course list block on category page and navigate to the course listing Given I log in as "admin" And I am on site homepage And I turn editing mode on And I am on course index And I follow "Category 1" And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Courses" block And I log out When I log in as "teacher1" And I am on course index And I follow "Category 1" Then I should see "Course 1" in the "My courses" "block" And I should see "Course 2" in the "My courses" "block" And I should see "Course 3" in the "My courses" "block" And I should not see "Course 4" in the "My courses" "block" And I follow "All courses" And I should see "Category 1" Scenario: Add the course list block on category page and navigate to another course Given I log in as "admin" And I am on site homepage And I turn editing mode on And I am on course index And I follow "Category 1" And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Courses" block And I log out When I log in as "teacher1" And I am on course index And I follow "Category 1" Then I should see "Course 1" in the "My courses" "block" And I should see "Course 2" in the "My courses" "block" And I should see "Course 3" in the "My courses" "block" And I should not see "Course 4" in the "My courses" "block" And I am on "Course 3" course homepage And I should see "Course 3" Scenario: Add the course list block on category page and view as an admin Given I log in as "admin" And I am on site homepage And I turn editing mode on And I am on course index And I follow "Category 1" And the following config values are set as admin: | unaddableblocks | | theme_boost| When I add the "Courses" block Then I should see "Category 1" in the "Course categories" "block" And I should see "Category A" in the "Course categories" "block" And I should see "Category B" in the "Course categories" "block" And I should not see "Category C" in the "Course categories" "block" And I should not see "Course 1" in the "Course categories" "block" And I should not see "Course 2" in the "Course categories" "block" And I follow "All courses" And I should see "Category 1" PK'9\_course_list/settings.phpnu[. /** * Course list block settings * * @package block_course_list * @copyright 2007 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { $options = array('all'=>get_string('allcourses', 'block_course_list'), 'own'=>get_string('owncourses', 'block_course_list')); $settings->add(new admin_setting_configselect('block_course_list_adminview', get_string('adminview', 'block_course_list'), get_string('configadminview', 'block_course_list'), 'all', $options)); $settings->add(new admin_setting_configcheckbox('block_course_list_hideallcourseslink', get_string('hideallcourseslink', 'block_course_list'), get_string('confighideallcourseslink', 'block_course_list'), 0)); } PK'9\search_forums/styles.cssnu[.block_search_forums .searchform { text-align: center; } .block_search_forums .searchform img { vertical-align: middle; } .block_search_forums .searchform img.resize { width: 1em; height: 1.1em; } .block_search_forums .invisiblefieldset { display: block; }PK'9\J&6hsearch_forums/db/access.phpnu[. /** * Search forums block caps. * * @package block_search_forums * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/search_forums:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), ); PK'9\uk-search_forums/lang/en/block_search_forums.phpnu[. /** * Strings for component 'block_search_forums', language 'en', branch 'MOODLE_20_STABLE' * * @package block_search_forums * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['advancedsearch'] = 'Advanced search'; $string['pluginname'] = 'Search forums'; $string['search_forums:addinstance'] = 'Add a new search forums block'; $string['privacy:metadata'] = 'The Search forums block only shows data stored in other locations.'; PK'9\ACsearch_forums/version.phpnu[. /** * Version details * * @package block_search_forums * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_search_forums'; // Full name of the plugin (used for diagnostics) $plugin->dependencies = ['mod_forum' => 2025040800]; PK'9\J85*search_forums/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for block_search_forums. * * @package block_search_forums * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_search_forums\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for block_search_forums implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } PK'9\w5//)search_forums/classes/output/renderer.phpnu[. /** * Block search forums renderer. * * @package block_search_forums * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_search_forums\output; defined('MOODLE_INTERNAL') || die(); use plugin_renderer_base; use renderable; /** * Block search forums renderer. * * @package block_search_forums * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends plugin_renderer_base { /** * Render search form. * * @param renderable $searchform The search form. * @return string */ public function render_search_form(renderable $searchform) { return $this->render_from_template('block_search_forums/search_form', $searchform->export_for_template($this)); } } PK'9\<,search_forums/classes/output/search_form.phpnu[. /** * Search form renderable. * * @package block_search_forums * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_search_forums\output; defined('MOODLE_INTERNAL') || die(); use help_icon; use moodle_url; use renderable; use renderer_base; use templatable; /** * Search form renderable class. * * @package block_search_forums * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class search_form implements renderable, templatable { /** @var int The course ID. */ protected $courseid; /** @var moodle_url The form action URL. */ protected $actionurl; /** @var help_icon The help icon. */ protected $helpicon; /** * Constructor. * * @param int $courseid The course ID. */ public function __construct($courseid) { $this->courseid = $courseid; $this->actionurl = new moodle_url('/mod/forum/search.php', ['id' => $courseid]); $this->helpicon = new help_icon('search', 'core'); } public function export_for_template(renderer_base $output) { $data = [ 'action' => $this->actionurl, 'helpicon' => $this->helpicon->export_for_template($output), 'hiddenfields' => (object) ['name' => 'id', 'value' => $this->courseid], 'inputname' => 'search', 'searchstring' => get_string('search') ]; return $data; } } PK'9\I&,search_forums/templates/search_form.mustachenu[{{! 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 . }} {{! @template block_search_forums/search_form This template renders the search form. Example context (json): { "action": "https://moodle.local/admin/search.php", "inputname": "search", "searchstring": "Search settings", "value": "policy", "hiddenfields": [ { "name": "course", "value": "11" } ], "helpicon": "" } }}
                {{>core/search_input}}
                {{#str}}advancedsearch, block_search_forums{{/str}} {{#helpicon}} {{>core/help_icon}} {{/helpicon}}
                PK'9\Uj j <search_forums/tests/behat/block_search_forums_course.featurenu[@block @block_search_forums @mod_forum Feature: The search forums block allows users to search for forum posts on course page In order to search for a forum post As a user I can use the search forums block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | 1 | teacher1@example.com | T1 | | student1 | Student | 1 | student1@example.com | S1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | news_items | Course | C1 | course-view-* | side-pre | | search_forums | Course | C1 | course-view-* | side-pre | And I am on the "Course 1" "course editing" page logged in as teacher1 And I set the field "id_newsitems" to "1" And I press "Save and display" And the following "mod_forum > discussions" exist: | user | forum | name | message | | teacher1 | Announcements | My subject | My message | Scenario: Use the search forum block in a course without any forum posts Given I am on the "Course 1" course page logged in as student1 When I set the field "Search" to "Moodle" And I press "Search" Then I should see "No posts" Scenario: Use the search forum block in a course with a hidden forum and search for posts Given I am on the "Announcements" "forum activity editing" page logged in as teacher1 And I expand all fieldsets And I set the field "id_visible" to "0" And I press "Save and return to course" When I am on the "Course 1" course page logged in as student1 And "Search forums" "block" should exist And I set the field "Search" to "message" And I press "Search" Then I should see "No posts" Scenario: Use the search forum block in a course and search for posts Given I am on the "Course 1" course page logged in as student1 And "Search forums" "block" should exist And I set the field "Search" to "message" And I press "Search" Then I should see "My subject" PK'9\ARrXX?search_forums/tests/behat/block_search_forums_frontpage.featurenu[@block @block_search_forums @mod_forum Feature: The search forums block allows users to search for forum posts on frontpage In order to search for a forum post As an administrator I can add the search forums block Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | student1 | Student | 1 | student1@example.com | S1 | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | search_forums | System | 1 | site-index | side-pre | Scenario: Use the search forum block on the frontpage and search for posts as a user Given I log in as "student1" And I am on site homepage When I set the field "Search" to "Moodle" And I press "Search" Then I should see "No posts" Scenario: Use the search forum block on the frontpage and search for posts as a guest Given I log in as "guest" And I am on site homepage When I set the field "Search" to "Moodle" And I press "Search" Then I should see "No posts" PK'9\Pޝ%search_forums/block_search_forums.phpnu[. /** * Block to search forum posts. * * @package block_search_forums * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_search_forums extends block_base { function init() { $this->title = get_string('pluginname', 'block_search_forums'); } function get_content() { global $CFG, $OUTPUT; if($this->content !== NULL) { return $this->content; } $this->content = new stdClass; $this->content->footer = ''; if (empty($this->instance)) { $this->content->text = ''; return $this->content; } $output = $this->page->get_renderer('block_search_forums'); $searchform = new \block_search_forums\output\search_form($this->page->course->id); $this->content->text = $output->render($searchform); return $this->content; } function applicable_formats() { return array('site' => true, 'course' => true); } /** * Returns the role that best describes the forum search block. * * @return string */ public function get_aria_role() { return 'search'; } } PKt\(99preformatted/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/preformatted", "title": "Preformatted", "category": "text", "description": "Add text that respects your spacing and tabs, and also allows styling.", "textdomain": "default", "attributes": { "content": { "type": "rich-text", "source": "rich-text", "selector": "pre", "__unstablePreserveWhiteSpace": true, "role": "content" } }, "supports": { "anchor": true, "color": { "gradients": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "spacing": { "padding": true, "margin": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "style": "wp-block-preformatted" } PKt\gvppreformatted/style.min.cssnu[.wp-block-preformatted{box-sizing:border-box;white-space:pre-wrap}:where(.wp-block-preformatted.has-background){padding:1.25em 2.375em}PKt\#preformatted/style-rtl.cssnu[.wp-block-preformatted{ box-sizing:border-box; white-space:pre-wrap; } :where(.wp-block-preformatted.has-background){ padding:1.25em 2.375em; }PKt\#preformatted/style.cssnu[.wp-block-preformatted{ box-sizing:border-box; white-space:pre-wrap; } :where(.wp-block-preformatted.has-background){ padding:1.25em 2.375em; }PKt\gvppreformatted/style-rtl.min.cssnu[.wp-block-preformatted{box-sizing:border-box;white-space:pre-wrap}:where(.wp-block-preformatted.has-background){padding:1.25em 2.375em}PKt\rss/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/rss", "title": "RSS", "category": "widgets", "description": "Display entries from any RSS or Atom feed.", "keywords": [ "atom", "feed" ], "textdomain": "default", "attributes": { "columns": { "type": "number", "default": 2 }, "blockLayout": { "type": "string", "default": "list" }, "feedURL": { "type": "string", "default": "" }, "itemsToShow": { "type": "number", "default": 5 }, "displayExcerpt": { "type": "boolean", "default": false }, "displayAuthor": { "type": "boolean", "default": false }, "displayDate": { "type": "boolean", "default": false }, "excerptLength": { "type": "number", "default": 55 } }, "supports": { "align": true, "html": false, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true }, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "padding": false, "margin": false } }, "color": { "background": true, "text": true, "gradients": true, "link": true } }, "editorStyle": "wp-block-rss-editor", "style": "wp-block-rss" } PKt\rss/editor.min.cssnu[.wp-block-rss li a>div{display:inline}.wp-block-rss__placeholder-form .wp-block-rss__placeholder-input{flex:1 1 auto}.wp-block-rss .wp-block-rss{all:inherit;margin:0;padding:0}PKt\ rss/style.min.cssnu[ul.wp-block-rss.alignleft{margin-right:2em}ul.wp-block-rss.alignright{margin-left:2em}ul.wp-block-rss.is-grid{display:flex;flex-wrap:wrap;padding:0}ul.wp-block-rss.is-grid li{margin:0 1em 1em 0;width:100%}@media (min-width:600px){ul.wp-block-rss.columns-2 li{width:calc(50% - 1em)}ul.wp-block-rss.columns-3 li{width:calc(33.33333% - 1em)}ul.wp-block-rss.columns-4 li{width:calc(25% - 1em)}ul.wp-block-rss.columns-5 li{width:calc(20% - 1em)}ul.wp-block-rss.columns-6 li{width:calc(16.66667% - 1em)}}.wp-block-rss__item-author,.wp-block-rss__item-publish-date{display:block;font-size:.8125em}.wp-block-rss{box-sizing:border-box;list-style:none;padding:0}PKt\Mgrss/style-rtl.cssnu[ul.wp-block-rss.alignleft{ margin-right:2em; } ul.wp-block-rss.alignright{ margin-left:2em; } ul.wp-block-rss.is-grid{ display:flex; flex-wrap:wrap; padding:0; } ul.wp-block-rss.is-grid li{ margin:0 0 1em 1em; width:100%; } @media (min-width:600px){ ul.wp-block-rss.columns-2 li{ width:calc(50% - 1em); } ul.wp-block-rss.columns-3 li{ width:calc(33.33333% - 1em); } ul.wp-block-rss.columns-4 li{ width:calc(25% - 1em); } ul.wp-block-rss.columns-5 li{ width:calc(20% - 1em); } ul.wp-block-rss.columns-6 li{ width:calc(16.66667% - 1em); } } .wp-block-rss__item-author,.wp-block-rss__item-publish-date{ display:block; font-size:.8125em; } .wp-block-rss{ box-sizing:border-box; list-style:none; padding:0; }PKt\@ srss/editor-rtl.cssnu[.wp-block-rss li a>div{ display:inline; } .wp-block-rss__placeholder-form .wp-block-rss__placeholder-input{ flex:1 1 auto; } .wp-block-rss .wp-block-rss{ all:inherit; margin:0; padding:0; }PKt\"~X rss/style.cssnu[ul.wp-block-rss.alignleft{ margin-right:2em; } ul.wp-block-rss.alignright{ margin-left:2em; } ul.wp-block-rss.is-grid{ display:flex; flex-wrap:wrap; padding:0; } ul.wp-block-rss.is-grid li{ margin:0 1em 1em 0; width:100%; } @media (min-width:600px){ ul.wp-block-rss.columns-2 li{ width:calc(50% - 1em); } ul.wp-block-rss.columns-3 li{ width:calc(33.33333% - 1em); } ul.wp-block-rss.columns-4 li{ width:calc(25% - 1em); } ul.wp-block-rss.columns-5 li{ width:calc(20% - 1em); } ul.wp-block-rss.columns-6 li{ width:calc(16.66667% - 1em); } } .wp-block-rss__item-author,.wp-block-rss__item-publish-date{ display:block; font-size:.8125em; } .wp-block-rss{ box-sizing:border-box; list-style:none; padding:0; }PKt\grss/style-rtl.min.cssnu[ul.wp-block-rss.alignleft{margin-right:2em}ul.wp-block-rss.alignright{margin-left:2em}ul.wp-block-rss.is-grid{display:flex;flex-wrap:wrap;padding:0}ul.wp-block-rss.is-grid li{margin:0 0 1em 1em;width:100%}@media (min-width:600px){ul.wp-block-rss.columns-2 li{width:calc(50% - 1em)}ul.wp-block-rss.columns-3 li{width:calc(33.33333% - 1em)}ul.wp-block-rss.columns-4 li{width:calc(25% - 1em)}ul.wp-block-rss.columns-5 li{width:calc(20% - 1em)}ul.wp-block-rss.columns-6 li{width:calc(16.66667% - 1em)}}.wp-block-rss__item-author,.wp-block-rss__item-publish-date{display:block;font-size:.8125em}.wp-block-rss{box-sizing:border-box;list-style:none;padding:0}PKt\rss/editor-rtl.min.cssnu[.wp-block-rss li a>div{display:inline}.wp-block-rss__placeholder-form .wp-block-rss__placeholder-input{flex:1 1 auto}.wp-block-rss .wp-block-rss{all:inherit;margin:0;padding:0}PKt\@ srss/editor.cssnu[.wp-block-rss li a>div{ display:inline; } .wp-block-rss__placeholder-form .wp-block-rss__placeholder-input{ flex:1 1 auto; } .wp-block-rss .wp-block-rss{ all:inherit; margin:0; padding:0; }PKt\d;"term-description/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/term-description", "title": "Term Description", "category": "theme", "description": "Display the description of categories, tags and custom taxonomies when viewing an archive.", "textdomain": "default", "attributes": { "textAlign": { "type": "string" } }, "supports": { "align": [ "wide", "full" ], "html": false, "color": { "link": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "spacing": { "padding": true, "margin": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } } } PKt\h*Kterm-description/style.min.cssnu[:where(.wp-block-term-description){box-sizing:border-box;margin-bottom:var(--wp--style--block-gap);margin-top:var(--wp--style--block-gap)}.wp-block-term-description p{margin-bottom:0;margin-top:0}PKt\ term-description/style-rtl.cssnu[:where(.wp-block-term-description){ box-sizing:border-box; margin-bottom:var(--wp--style--block-gap); margin-top:var(--wp--style--block-gap); } .wp-block-term-description p{ margin-bottom:0; margin-top:0; }PKt\ term-description/style.cssnu[:where(.wp-block-term-description){ box-sizing:border-box; margin-bottom:var(--wp--style--block-gap); margin-top:var(--wp--style--block-gap); } .wp-block-term-description p{ margin-bottom:0; margin-top:0; }PKt\h*K"term-description/style-rtl.min.cssnu[:where(.wp-block-term-description){box-sizing:border-box;margin-bottom:var(--wp--style--block-gap);margin-top:var(--wp--style--block-gap)}.wp-block-term-description p{margin-bottom:0;margin-top:0}PKt\'[CCrequire-dynamic-blocks.phpnu[ $classes, ) ); // Set default values. $format = '%link'; $link = 'next' === $navigation_type ? _x( 'Next', 'label for next post link' ) : _x( 'Previous', 'label for previous post link' ); $label = ''; // Only use hardcoded values here, otherwise we need to add escaping where these values are used. $arrow_map = array( 'none' => '', 'arrow' => array( 'next' => '→', 'previous' => '←', ), 'chevron' => array( 'next' => '»', 'previous' => '«', ), ); // If a custom label is provided, make this a link. // `$label` is used to prepend the provided label, if we want to show the page title as well. if ( isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ) { $label = "{$attributes['label']}"; $link = $label; } // If we want to also show the page title, make the page title a link and prepend the label. if ( isset( $attributes['showTitle'] ) && $attributes['showTitle'] ) { /* * If the label link option is not enabled but there is a custom label, * display the custom label as text before the linked title. */ if ( ! $attributes['linkLabel'] ) { if ( $label ) { $format = '' . wp_kses_post( $label ) . ' %link'; } $link = '%title'; } elseif ( isset( $attributes['linkLabel'] ) && $attributes['linkLabel'] ) { // If the label link option is enabled and there is a custom label, display it before the title. if ( $label ) { $link = '' . wp_kses_post( $label ) . ' %title'; } else { /* * If the label link option is enabled and there is no custom label, * add a colon between the label and the post title. */ $label = 'next' === $navigation_type ? _x( 'Next:', 'label before the title of the next post' ) : _x( 'Previous:', 'label before the title of the previous post' ); $link = sprintf( '%1$s %2$s', wp_kses_post( $label ), '%title' ); } } } // Display arrows. if ( isset( $attributes['arrow'] ) && 'none' !== $attributes['arrow'] && isset( $arrow_map[ $attributes['arrow'] ] ) ) { $arrow = $arrow_map[ $attributes['arrow'] ][ $navigation_type ]; if ( 'next' === $navigation_type ) { $format = '%link'; } else { $format = '%link'; } } /* * The dynamic portion of the function name, `$navigation_type`, * Refers to the type of adjacency, 'next' or 'previous'. * * @see https://developer.wordpress.org/reference/functions/get_previous_post_link/ * @see https://developer.wordpress.org/reference/functions/get_next_post_link/ */ $get_link_function = "get_{$navigation_type}_post_link"; if ( ! empty( $attributes['taxonomy'] ) ) { $content = $get_link_function( $format, $link, true, '', $attributes['taxonomy'] ); } else { $content = $get_link_function( $format, $link ); } return sprintf( '
                %2$s
                ', $wrapper_attributes, $content ); } /** * Registers the `core/post-navigation-link` block on the server. * * @since 5.9.0 */ function register_block_core_post_navigation_link() { register_block_type_from_metadata( __DIR__ . '/post-navigation-link', array( 'render_callback' => 'render_block_core_post_navigation_link', ) ); } add_action( 'init', 'register_block_core_post_navigation_link' ); PKt\Anextpage/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/nextpage", "title": "Page Break", "category": "design", "description": "Separate your content into a multi-page experience.", "keywords": [ "next page", "pagination" ], "parent": [ "core/post-content" ], "textdomain": "default", "supports": { "customClassName": false, "className": false, "html": false, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-nextpage-editor" } PKt\IPPnextpage/editor.min.cssnu[.block-editor-block-list__block[data-type="core/nextpage"]{margin-bottom:28px;margin-top:28px;max-width:100%;text-align:center}.wp-block-nextpage{display:block;text-align:center;white-space:nowrap}.wp-block-nextpage>span{background:#fff;border-radius:4px;color:#757575;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;font-weight:600;height:24px;padding:6px 8px;position:relative;text-transform:uppercase}.wp-block-nextpage:before{border-top:3px dashed #ccc;content:"";left:0;position:absolute;right:0;top:50%}PKt\Mynextpage/editor-rtl.cssnu[.block-editor-block-list__block[data-type="core/nextpage"]{ margin-bottom:28px; margin-top:28px; max-width:100%; text-align:center; } .wp-block-nextpage{ display:block; text-align:center; white-space:nowrap; } .wp-block-nextpage>span{ background:#fff; border-radius:4px; color:#757575; font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif; font-size:13px; font-weight:600; height:24px; padding:6px 8px; position:relative; text-transform:uppercase; } .wp-block-nextpage:before{ border-top:3px dashed #ccc; content:""; left:0; position:absolute; right:0; top:50%; }PKt\IPPnextpage/editor-rtl.min.cssnu[.block-editor-block-list__block[data-type="core/nextpage"]{margin-bottom:28px;margin-top:28px;max-width:100%;text-align:center}.wp-block-nextpage{display:block;text-align:center;white-space:nowrap}.wp-block-nextpage>span{background:#fff;border-radius:4px;color:#757575;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;font-weight:600;height:24px;padding:6px 8px;position:relative;text-transform:uppercase}.wp-block-nextpage:before{border-top:3px dashed #ccc;content:"";left:0;position:absolute;right:0;top:50%}PKt\Mynextpage/editor.cssnu[.block-editor-block-list__block[data-type="core/nextpage"]{ margin-bottom:28px; margin-top:28px; max-width:100%; text-align:center; } .wp-block-nextpage{ display:block; text-align:center; white-space:nowrap; } .wp-block-nextpage>span{ background:#fff; border-radius:4px; color:#757575; font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif; font-size:13px; font-weight:600; height:24px; padding:6px 8px; position:relative; text-transform:uppercase; } .wp-block-nextpage:before{ border-top:3px dashed #ccc; content:""; left:0; position:absolute; right:0; top:50%; }PKt\\ image/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/image", "title": "Image", "category": "media", "usesContext": [ "allowResize", "imageCrop", "fixedHeight", "postId", "postType", "queryId" ], "description": "Insert an image to make a visual statement.", "keywords": [ "img", "photo", "picture" ], "textdomain": "default", "attributes": { "blob": { "type": "string", "role": "local" }, "url": { "type": "string", "source": "attribute", "selector": "img", "attribute": "src", "role": "content" }, "alt": { "type": "string", "source": "attribute", "selector": "img", "attribute": "alt", "default": "", "role": "content" }, "caption": { "type": "rich-text", "source": "rich-text", "selector": "figcaption", "role": "content" }, "lightbox": { "type": "object", "enabled": { "type": "boolean" } }, "title": { "type": "string", "source": "attribute", "selector": "img", "attribute": "title", "role": "content" }, "href": { "type": "string", "source": "attribute", "selector": "figure > a", "attribute": "href", "role": "content" }, "rel": { "type": "string", "source": "attribute", "selector": "figure > a", "attribute": "rel" }, "linkClass": { "type": "string", "source": "attribute", "selector": "figure > a", "attribute": "class" }, "id": { "type": "number", "role": "content" }, "width": { "type": "string" }, "height": { "type": "string" }, "aspectRatio": { "type": "string" }, "scale": { "type": "string" }, "sizeSlug": { "type": "string" }, "linkDestination": { "type": "string" }, "linkTarget": { "type": "string", "source": "attribute", "selector": "figure > a", "attribute": "target" } }, "supports": { "interactivity": true, "align": [ "left", "center", "right", "wide", "full" ], "anchor": true, "color": { "text": false, "background": false }, "filter": { "duotone": true }, "spacing": { "margin": true }, "__experimentalBorder": { "color": true, "radius": true, "width": true, "__experimentalSkipSerialization": true, "__experimentalDefaultControls": { "color": true, "radius": true, "width": true } }, "shadow": { "__experimentalSkipSerialization": true } }, "selectors": { "border": ".wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder", "shadow": ".wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder", "filter": { "duotone": ".wp-block-image img, .wp-block-image .components-placeholder" } }, "styles": [ { "name": "default", "label": "Default", "isDefault": true }, { "name": "rounded", "label": "Rounded" } ], "editorStyle": "wp-block-image-editor", "style": "wp-block-image" } PKt\1IK image/editor.min.cssnu[.wp-block-image.wp-block-image .block-editor-media-placeholder.is-small{min-height:60px}figure.wp-block-image:not(.wp-block){margin:0}.wp-block-image{position:relative}.wp-block-image .is-applying img,.wp-block-image.is-transient img{opacity:.3}.wp-block-image figcaption img{display:inline}.wp-block-image .components-spinner{left:50%;margin:0;position:absolute;top:50%;transform:translate(-50%,-50%)}.wp-block-image__placeholder{aspect-ratio:4/3}.wp-block-image__placeholder.has-illustration:before{background:#fff;opacity:.8}.wp-block-image__placeholder .components-placeholder__illustration{opacity:.1}.wp-block-image .components-resizable-box__container{display:table}.wp-block-image .components-resizable-box__container img{display:block;height:inherit;width:inherit}.block-editor-block-list__block[data-type="core/image"] .block-editor-block-toolbar .block-editor-url-input__button-modal{left:0;margin:-1px 0;position:absolute;right:0}@media (min-width:600px){.block-editor-block-list__block[data-type="core/image"] .block-editor-block-toolbar .block-editor-url-input__button-modal{margin:-1px}}[data-align=full]>.wp-block-image img,[data-align=wide]>.wp-block-image img{height:auto;width:100%}.wp-block[data-align=center]>.wp-block-image,.wp-block[data-align=left]>.wp-block-image,.wp-block[data-align=right]>.wp-block-image{display:table}.wp-block[data-align=center]>.wp-block-image>figcaption,.wp-block[data-align=left]>.wp-block-image>figcaption,.wp-block[data-align=right]>.wp-block-image>figcaption{caption-side:bottom;display:table-caption}.wp-block[data-align=left]>.wp-block-image{margin:.5em 1em .5em 0}.wp-block[data-align=right]>.wp-block-image{margin:.5em 0 .5em 1em}.wp-block[data-align=center]>.wp-block-image{margin-left:auto;margin-right:auto;text-align:center}.wp-block[data-align]:has(>.wp-block-image){position:relative}.wp-block-image__crop-area{max-width:100%;overflow:hidden;position:relative;width:100%}.wp-block-image__crop-area .reactEasyCrop_Container{pointer-events:auto}.wp-block-image__crop-area .reactEasyCrop_Container .reactEasyCrop_Image{border:none;border-radius:0}.wp-block-image__crop-icon{align-items:center;display:flex;justify-content:center;min-width:48px;padding:0 8px}.wp-block-image__crop-icon svg{fill:currentColor}.wp-block-image__zoom .components-popover__content{min-width:260px;overflow:visible!important}.wp-block-image__toolbar_content_textarea__container{padding:8px}.wp-block-image__toolbar_content_textarea{width:250px}PKt\.image/theme.min.cssnu[:root :where(.wp-block-image figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme :root :where(.wp-block-image figcaption){color:#ffffffa6}.wp-block-image{margin:0 0 1em}PKt\4$TTimage/view.min.asset.phpnu[ array(), 'version' => 'ff354d5368d64857fef0'); PKt\'=KGG image/view.jsnu[import * as __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__ from "@wordpress/interactivity"; /******/ // The require scope /******/ var __webpack_require__ = {}; /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; ;// external "@wordpress/interactivity" var x = (y) => { var x = {}; __webpack_require__.d(x, y); return x } var y = (x) => (() => (x)) const interactivity_namespaceObject = x({ ["getContext"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getContext), ["getElement"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getElement), ["store"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.store), ["withSyncEvent"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.withSyncEvent) }); ;// ./node_modules/@wordpress/block-library/build-module/image/view.js /** * WordPress dependencies */ /** * Tracks whether user is touching screen; used to differentiate behavior for * touch and mouse input. * * @type {boolean} */ let isTouching = false; /** * Tracks the last time the screen was touched; used to differentiate behavior * for touch and mouse input. * * @type {number} */ let lastTouchTime = 0; const { state, actions, callbacks } = (0,interactivity_namespaceObject.store)('core/image', { state: { currentImageId: null, get currentImage() { return state.metadata[state.currentImageId]; }, get overlayOpened() { return state.currentImageId !== null; }, get roleAttribute() { return state.overlayOpened ? 'dialog' : null; }, get ariaModal() { return state.overlayOpened ? 'true' : null; }, get enlargedSrc() { return state.currentImage.uploadedSrc || 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; }, get figureStyles() { return state.overlayOpened && `${state.currentImage.figureStyles?.replace(/margin[^;]*;?/g, '')};`; }, get imgStyles() { return state.overlayOpened && `${state.currentImage.imgStyles?.replace(/;$/, '')}; object-fit:cover;`; }, get imageButtonRight() { const { imageId } = (0,interactivity_namespaceObject.getContext)(); return state.metadata[imageId].imageButtonRight; }, get imageButtonTop() { const { imageId } = (0,interactivity_namespaceObject.getContext)(); return state.metadata[imageId].imageButtonTop; }, get isContentHidden() { const ctx = (0,interactivity_namespaceObject.getContext)(); return state.overlayEnabled && state.currentImageId === ctx.imageId; }, get isContentVisible() { const ctx = (0,interactivity_namespaceObject.getContext)(); return !state.overlayEnabled && state.currentImageId === ctx.imageId; } }, actions: { showLightbox() { const { imageId } = (0,interactivity_namespaceObject.getContext)(); // Bails out if the image has not loaded yet. if (!state.metadata[imageId].imageRef?.complete) { return; } // Stores the positions of the scroll to fix it until the overlay is // closed. state.scrollTopReset = document.documentElement.scrollTop; state.scrollLeftReset = document.documentElement.scrollLeft; // Sets the current expanded image in the state and enables the overlay. state.overlayEnabled = true; state.currentImageId = imageId; // Computes the styles of the overlay for the animation. callbacks.setOverlayStyles(); }, hideLightbox() { if (state.overlayEnabled) { // Starts the overlay closing animation. The showClosingAnimation // class is used to avoid showing it on page load. state.showClosingAnimation = true; state.overlayEnabled = false; // Waits until the close animation has completed before allowing a // user to scroll again. The duration of this animation is defined in // the `styles.scss` file, but in any case we should wait a few // milliseconds longer than the duration, otherwise a user may scroll // too soon and cause the animation to look sloppy. setTimeout(function () { // Delays before changing the focus. Otherwise the focus ring will // appear on Firefox before the image has finished animating, which // looks broken. state.currentImage.buttonRef.focus({ preventScroll: true }); // Resets the current image id to mark the overlay as closed. state.currentImageId = null; }, 450); } }, handleKeydown: (0,interactivity_namespaceObject.withSyncEvent)(event => { if (state.overlayEnabled) { // Focuses the close button when the user presses the tab key. if (event.key === 'Tab') { event.preventDefault(); const { ref } = (0,interactivity_namespaceObject.getElement)(); ref.querySelector('button').focus(); } // Closes the lightbox when the user presses the escape key. if (event.key === 'Escape') { actions.hideLightbox(); } } }), handleTouchMove: (0,interactivity_namespaceObject.withSyncEvent)(event => { // On mobile devices, prevents triggering the scroll event because // otherwise the page jumps around when it resets the scroll position. // This also means that closing the lightbox requires that a user // perform a simple tap. This may be changed in the future if there is a // better alternative to override or reset the scroll position during // swipe actions. if (state.overlayEnabled) { event.preventDefault(); } }), handleTouchStart() { isTouching = true; }, handleTouchEnd() { // Waits a few milliseconds before resetting to ensure that pinch to // zoom works consistently on mobile devices when the lightbox is open. lastTouchTime = Date.now(); isTouching = false; }, handleScroll() { // Prevents scrolling behaviors that trigger content shift while the // lightbox is open. It would be better to accomplish through CSS alone, // but using overflow: hidden is currently the only way to do so and // that causes a layout to shift and prevents the zoom animation from // working in some cases because it's not possible to account for the // layout shift when doing the animation calculations. Instead, it uses // JavaScript to prevent and reset the scrolling behavior. if (state.overlayOpened) { // Avoids overriding the scroll behavior on mobile devices because // doing so breaks the pinch to zoom functionality, and users should // be able to zoom in further on the high-res image. if (!isTouching && Date.now() - lastTouchTime > 450) { // It doesn't rely on `event.preventDefault()` to prevent scrolling // because the scroll event can't be canceled, so it resets the // position instead. window.scrollTo(state.scrollLeftReset, state.scrollTopReset); } } } }, callbacks: { setOverlayStyles() { if (!state.overlayEnabled) { return; } let { naturalWidth, naturalHeight, offsetWidth: originalWidth, offsetHeight: originalHeight } = state.currentImage.imageRef; let { x: screenPosX, y: screenPosY } = state.currentImage.imageRef.getBoundingClientRect(); // Natural ratio of the image clicked to open the lightbox. const naturalRatio = naturalWidth / naturalHeight; // Original ratio of the image clicked to open the lightbox. let originalRatio = originalWidth / originalHeight; // If it has object-fit: contain, recalculates the original sizes // and the screen position without the blank spaces. if (state.currentImage.scaleAttr === 'contain') { if (naturalRatio > originalRatio) { const heightWithoutSpace = originalWidth / naturalRatio; // Recalculates screen position without the top space. screenPosY += (originalHeight - heightWithoutSpace) / 2; originalHeight = heightWithoutSpace; } else { const widthWithoutSpace = originalHeight * naturalRatio; // Recalculates screen position without the left space. screenPosX += (originalWidth - widthWithoutSpace) / 2; originalWidth = widthWithoutSpace; } } originalRatio = originalWidth / originalHeight; // Typically, it uses the image's full-sized dimensions. If those // dimensions have not been set (i.e. an external image with only one // size), the image's dimensions in the lightbox are the same // as those of the image in the content. let imgMaxWidth = parseFloat(state.currentImage.targetWidth !== 'none' ? state.currentImage.targetWidth : naturalWidth); let imgMaxHeight = parseFloat(state.currentImage.targetHeight !== 'none' ? state.currentImage.targetHeight : naturalHeight); // Ratio of the biggest image stored in the database. let imgRatio = imgMaxWidth / imgMaxHeight; let containerMaxWidth = imgMaxWidth; let containerMaxHeight = imgMaxHeight; let containerWidth = imgMaxWidth; let containerHeight = imgMaxHeight; // Checks if the target image has a different ratio than the original // one (thumbnail). Recalculates the width and height. if (naturalRatio.toFixed(2) !== imgRatio.toFixed(2)) { if (naturalRatio > imgRatio) { // If the width is reached before the height, it keeps the maxWidth // and recalculates the height unless the difference between the // maxHeight and the reducedHeight is higher than the maxWidth, // where it keeps the reducedHeight and recalculate the width. const reducedHeight = imgMaxWidth / naturalRatio; if (imgMaxHeight - reducedHeight > imgMaxWidth) { imgMaxHeight = reducedHeight; imgMaxWidth = reducedHeight * naturalRatio; } else { imgMaxHeight = imgMaxWidth / naturalRatio; } } else { // If the height is reached before the width, it keeps the maxHeight // and recalculate the width unlesss the difference between the // maxWidth and the reducedWidth is higher than the maxHeight, where // it keeps the reducedWidth and recalculate the height. const reducedWidth = imgMaxHeight * naturalRatio; if (imgMaxWidth - reducedWidth > imgMaxHeight) { imgMaxWidth = reducedWidth; imgMaxHeight = reducedWidth / naturalRatio; } else { imgMaxWidth = imgMaxHeight * naturalRatio; } } containerWidth = imgMaxWidth; containerHeight = imgMaxHeight; imgRatio = imgMaxWidth / imgMaxHeight; // Calculates the max size of the container. if (originalRatio > imgRatio) { containerMaxWidth = imgMaxWidth; containerMaxHeight = containerMaxWidth / originalRatio; } else { containerMaxHeight = imgMaxHeight; containerMaxWidth = containerMaxHeight * originalRatio; } } // If the image has been pixelated on purpose, it keeps that size. if (originalWidth > containerWidth || originalHeight > containerHeight) { containerWidth = originalWidth; containerHeight = originalHeight; } // Calculates the final lightbox image size and the scale factor. // MaxWidth is either the window container (accounting for padding) or // the image resolution. let horizontalPadding = 0; if (window.innerWidth > 480) { horizontalPadding = 80; } else if (window.innerWidth > 1920) { horizontalPadding = 160; } const verticalPadding = 80; const targetMaxWidth = Math.min(window.innerWidth - horizontalPadding, containerWidth); const targetMaxHeight = Math.min(window.innerHeight - verticalPadding, containerHeight); const targetContainerRatio = targetMaxWidth / targetMaxHeight; if (originalRatio > targetContainerRatio) { // If targetMaxWidth is reached before targetMaxHeight. containerWidth = targetMaxWidth; containerHeight = containerWidth / originalRatio; } else { // If targetMaxHeight is reached before targetMaxWidth. containerHeight = targetMaxHeight; containerWidth = containerHeight * originalRatio; } const containerScale = originalWidth / containerWidth; const lightboxImgWidth = imgMaxWidth * (containerWidth / containerMaxWidth); const lightboxImgHeight = imgMaxHeight * (containerHeight / containerMaxHeight); // As of this writing, using the calculations above will render the // lightbox with a small, erroneous whitespace on the left side of the // image in iOS Safari, perhaps due to an inconsistency in how browsers // handle absolute positioning and CSS transformation. In any case, // adding 1 pixel to the container width and height solves the problem, // though this can be removed if the issue is fixed in the future. state.overlayStyles = ` --wp--lightbox-initial-top-position: ${screenPosY}px; --wp--lightbox-initial-left-position: ${screenPosX}px; --wp--lightbox-container-width: ${containerWidth + 1}px; --wp--lightbox-container-height: ${containerHeight + 1}px; --wp--lightbox-image-width: ${lightboxImgWidth}px; --wp--lightbox-image-height: ${lightboxImgHeight}px; --wp--lightbox-scale: ${containerScale}; --wp--lightbox-scrollbar-width: ${window.innerWidth - document.documentElement.clientWidth}px; `; }, setButtonStyles() { const { imageId } = (0,interactivity_namespaceObject.getContext)(); const { ref } = (0,interactivity_namespaceObject.getElement)(); state.metadata[imageId].imageRef = ref; state.metadata[imageId].currentSrc = ref.currentSrc; const { naturalWidth, naturalHeight, offsetWidth, offsetHeight } = ref; // If the image isn't loaded yet, it can't calculate where the button // should be. if (naturalWidth === 0 || naturalHeight === 0) { return; } const figure = ref.parentElement; const figureWidth = ref.parentElement.clientWidth; // It needs special handling for the height because a caption will cause // the figure to be taller than the image, which means it needs to // account for that when calculating the placement of the button in the // top right corner of the image. let figureHeight = ref.parentElement.clientHeight; const caption = figure.querySelector('figcaption'); if (caption) { const captionComputedStyle = window.getComputedStyle(caption); if (!['absolute', 'fixed'].includes(captionComputedStyle.position)) { figureHeight = figureHeight - caption.offsetHeight - parseFloat(captionComputedStyle.marginTop) - parseFloat(captionComputedStyle.marginBottom); } } const buttonOffsetTop = figureHeight - offsetHeight; const buttonOffsetRight = figureWidth - offsetWidth; let imageButtonTop = buttonOffsetTop + 16; let imageButtonRight = buttonOffsetRight + 16; // In the case of an image with object-fit: contain, the size of the // element can be larger than the image itself, so it needs to // calculate where to place the button. if (state.metadata[imageId].scaleAttr === 'contain') { // Natural ratio of the image. const naturalRatio = naturalWidth / naturalHeight; // Offset ratio of the image. const offsetRatio = offsetWidth / offsetHeight; if (naturalRatio >= offsetRatio) { // If it reaches the width first, it keeps the width and compute the // height. const referenceHeight = offsetWidth / naturalRatio; imageButtonTop = (offsetHeight - referenceHeight) / 2 + buttonOffsetTop + 16; imageButtonRight = buttonOffsetRight + 16; } else { // If it reaches the height first, it keeps the height and compute // the width. const referenceWidth = offsetHeight * naturalRatio; imageButtonTop = buttonOffsetTop + 16; imageButtonRight = (offsetWidth - referenceWidth) / 2 + buttonOffsetRight + 16; } } state.metadata[imageId].imageButtonTop = imageButtonTop; state.metadata[imageId].imageButtonRight = imageButtonRight; }, setOverlayFocus() { if (state.overlayEnabled) { // Moves the focus to the dialog when it opens. const { ref } = (0,interactivity_namespaceObject.getElement)(); ref.focus(); } }, initTriggerButton() { const { imageId } = (0,interactivity_namespaceObject.getContext)(); const { ref } = (0,interactivity_namespaceObject.getElement)(); state.metadata[imageId].buttonRef = ref; } } }, { lock: true }); PKt\Yimage/theme.cssnu[:root :where(.wp-block-image figcaption){ color:#555; font-size:13px; text-align:center; } .is-dark-theme :root :where(.wp-block-image figcaption){ color:#ffffffa6; } .wp-block-image{ margin:0 0 1em; }PKt\y image/style.min.cssnu[.wp-block-image>a,.wp-block-image>figure>a{display:inline-block}.wp-block-image img{box-sizing:border-box;height:auto;max-width:100%;vertical-align:bottom}@media not (prefers-reduced-motion){.wp-block-image img.hide{visibility:hidden}.wp-block-image img.show{animation:show-content-image .4s}}.wp-block-image[style*=border-radius] img,.wp-block-image[style*=border-radius]>a{border-radius:inherit}.wp-block-image.has-custom-border img{box-sizing:border-box}.wp-block-image.aligncenter{text-align:center}.wp-block-image.alignfull>a,.wp-block-image.alignwide>a{width:100%}.wp-block-image.alignfull img,.wp-block-image.alignwide img{height:auto;width:100%}.wp-block-image .aligncenter,.wp-block-image .alignleft,.wp-block-image .alignright,.wp-block-image.aligncenter,.wp-block-image.alignleft,.wp-block-image.alignright{display:table}.wp-block-image .aligncenter>figcaption,.wp-block-image .alignleft>figcaption,.wp-block-image .alignright>figcaption,.wp-block-image.aligncenter>figcaption,.wp-block-image.alignleft>figcaption,.wp-block-image.alignright>figcaption{caption-side:bottom;display:table-caption}.wp-block-image .alignleft{float:left;margin:.5em 1em .5em 0}.wp-block-image .alignright{float:right;margin:.5em 0 .5em 1em}.wp-block-image .aligncenter{margin-left:auto;margin-right:auto}.wp-block-image :where(figcaption){margin-bottom:1em;margin-top:.5em}.wp-block-image.is-style-circle-mask img{border-radius:9999px}@supports ((-webkit-mask-image:none) or (mask-image:none)) or (-webkit-mask-image:none){.wp-block-image.is-style-circle-mask img{border-radius:0;-webkit-mask-image:url('data:image/svg+xml;utf8,');mask-image:url('data:image/svg+xml;utf8,');mask-mode:alpha;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}}:root :where(.wp-block-image.is-style-rounded img,.wp-block-image .is-style-rounded img){border-radius:9999px}.wp-block-image figure{margin:0}.wp-lightbox-container{display:flex;flex-direction:column;position:relative}.wp-lightbox-container img{cursor:zoom-in}.wp-lightbox-container img:hover+button{opacity:1}.wp-lightbox-container button{align-items:center;-webkit-backdrop-filter:blur(16px) saturate(180%);backdrop-filter:blur(16px) saturate(180%);background-color:#5a5a5a40;border:none;border-radius:4px;cursor:zoom-in;display:flex;height:20px;justify-content:center;opacity:0;padding:0;position:absolute;right:16px;text-align:center;top:16px;width:20px;z-index:100}@media not (prefers-reduced-motion){.wp-lightbox-container button{transition:opacity .2s ease}}.wp-lightbox-container button:focus-visible{outline:3px auto #5a5a5a40;outline:3px auto -webkit-focus-ring-color;outline-offset:3px}.wp-lightbox-container button:hover{cursor:pointer;opacity:1}.wp-lightbox-container button:focus{opacity:1}.wp-lightbox-container button:focus,.wp-lightbox-container button:hover,.wp-lightbox-container button:not(:hover):not(:active):not(.has-background){background-color:#5a5a5a40;border:none}.wp-lightbox-overlay{box-sizing:border-box;cursor:zoom-out;height:100vh;left:0;overflow:hidden;position:fixed;top:0;visibility:hidden;width:100%;z-index:100000}.wp-lightbox-overlay .close-button{align-items:center;cursor:pointer;display:flex;justify-content:center;min-height:40px;min-width:40px;padding:0;position:absolute;right:calc(env(safe-area-inset-right) + 16px);top:calc(env(safe-area-inset-top) + 16px);z-index:5000000}.wp-lightbox-overlay .close-button:focus,.wp-lightbox-overlay .close-button:hover,.wp-lightbox-overlay .close-button:not(:hover):not(:active):not(.has-background){background:none;border:none}.wp-lightbox-overlay .lightbox-image-container{height:var(--wp--lightbox-container-height);left:50%;overflow:hidden;position:absolute;top:50%;transform:translate(-50%,-50%);transform-origin:top left;width:var(--wp--lightbox-container-width);z-index:9999999999}.wp-lightbox-overlay .wp-block-image{align-items:center;box-sizing:border-box;display:flex;height:100%;justify-content:center;margin:0;position:relative;transform-origin:0 0;width:100%;z-index:3000000}.wp-lightbox-overlay .wp-block-image img{height:var(--wp--lightbox-image-height);min-height:var(--wp--lightbox-image-height);min-width:var(--wp--lightbox-image-width);width:var(--wp--lightbox-image-width)}.wp-lightbox-overlay .wp-block-image figcaption{display:none}.wp-lightbox-overlay button{background:none;border:none}.wp-lightbox-overlay .scrim{background-color:#fff;height:100%;opacity:.9;position:absolute;width:100%;z-index:2000000}.wp-lightbox-overlay.active{visibility:visible}@media not (prefers-reduced-motion){.wp-lightbox-overlay.active{animation:turn-on-visibility .25s both}.wp-lightbox-overlay.active img{animation:turn-on-visibility .35s both}.wp-lightbox-overlay.show-closing-animation:not(.active){animation:turn-off-visibility .35s both}.wp-lightbox-overlay.show-closing-animation:not(.active) img{animation:turn-off-visibility .25s both}.wp-lightbox-overlay.zoom.active{animation:none;opacity:1;visibility:visible}.wp-lightbox-overlay.zoom.active .lightbox-image-container{animation:lightbox-zoom-in .4s}.wp-lightbox-overlay.zoom.active .lightbox-image-container img{animation:none}.wp-lightbox-overlay.zoom.active .scrim{animation:turn-on-visibility .4s forwards}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active){animation:none}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container{animation:lightbox-zoom-out .4s}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container img{animation:none}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .scrim{animation:turn-off-visibility .4s forwards}}@keyframes show-content-image{0%{visibility:hidden}99%{visibility:hidden}to{visibility:visible}}@keyframes turn-on-visibility{0%{opacity:0}to{opacity:1}}@keyframes turn-off-visibility{0%{opacity:1;visibility:visible}99%{opacity:0;visibility:visible}to{opacity:0;visibility:hidden}}@keyframes lightbox-zoom-in{0%{transform:translate(calc((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position)),calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale))}to{transform:translate(-50%,-50%) scale(1)}}@keyframes lightbox-zoom-out{0%{transform:translate(-50%,-50%) scale(1);visibility:visible}99%{visibility:visible}to{transform:translate(calc((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position)),calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale));visibility:hidden}}PKt\oc\TTimage/view.asset.phpnu[ array(), 'version' => '7500eb032759d407a71d'); PKt\c@wYGGimage/style-rtl.cssnu[.wp-block-image>a,.wp-block-image>figure>a{ display:inline-block; } .wp-block-image img{ box-sizing:border-box; height:auto; max-width:100%; vertical-align:bottom; } @media not (prefers-reduced-motion){ .wp-block-image img.hide{ visibility:hidden; } .wp-block-image img.show{ animation:show-content-image .4s; } } .wp-block-image[style*=border-radius] img,.wp-block-image[style*=border-radius]>a{ border-radius:inherit; } .wp-block-image.has-custom-border img{ box-sizing:border-box; } .wp-block-image.aligncenter{ text-align:center; } .wp-block-image.alignfull>a,.wp-block-image.alignwide>a{ width:100%; } .wp-block-image.alignfull img,.wp-block-image.alignwide img{ height:auto; width:100%; } .wp-block-image .aligncenter,.wp-block-image .alignleft,.wp-block-image .alignright,.wp-block-image.aligncenter,.wp-block-image.alignleft,.wp-block-image.alignright{ display:table; } .wp-block-image .aligncenter>figcaption,.wp-block-image .alignleft>figcaption,.wp-block-image .alignright>figcaption,.wp-block-image.aligncenter>figcaption,.wp-block-image.alignleft>figcaption,.wp-block-image.alignright>figcaption{ caption-side:bottom; display:table-caption; } .wp-block-image .alignleft{ float:left; margin:.5em 1em .5em 0; } .wp-block-image .alignright{ float:right; margin:.5em 0 .5em 1em; } .wp-block-image .aligncenter{ margin-left:auto; margin-right:auto; } .wp-block-image :where(figcaption){ margin-bottom:1em; margin-top:.5em; } .wp-block-image.is-style-circle-mask img{ border-radius:9999px; } @supports ((-webkit-mask-image:none) or (mask-image:none)) or (-webkit-mask-image:none){ .wp-block-image.is-style-circle-mask img{ border-radius:0; -webkit-mask-image:url('data:image/svg+xml;utf8,'); mask-image:url('data:image/svg+xml;utf8,'); mask-mode:alpha; -webkit-mask-position:center; mask-position:center; -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat; -webkit-mask-size:contain; mask-size:contain; } } :root :where(.wp-block-image.is-style-rounded img,.wp-block-image .is-style-rounded img){ border-radius:9999px; } .wp-block-image figure{ margin:0; } .wp-lightbox-container{ display:flex; flex-direction:column; position:relative; } .wp-lightbox-container img{ cursor:zoom-in; } .wp-lightbox-container img:hover+button{ opacity:1; } .wp-lightbox-container button{ align-items:center; -webkit-backdrop-filter:blur(16px) saturate(180%); backdrop-filter:blur(16px) saturate(180%); background-color:#5a5a5a40; border:none; border-radius:4px; cursor:zoom-in; display:flex; height:20px; justify-content:center; left:16px; opacity:0; padding:0; position:absolute; text-align:center; top:16px; width:20px; z-index:100; } @media not (prefers-reduced-motion){ .wp-lightbox-container button{ transition:opacity .2s ease; } } .wp-lightbox-container button:focus-visible{ outline:3px auto #5a5a5a40; outline:3px auto -webkit-focus-ring-color; outline-offset:3px; } .wp-lightbox-container button:hover{ cursor:pointer; opacity:1; } .wp-lightbox-container button:focus{ opacity:1; } .wp-lightbox-container button:focus,.wp-lightbox-container button:hover,.wp-lightbox-container button:not(:hover):not(:active):not(.has-background){ background-color:#5a5a5a40; border:none; } .wp-lightbox-overlay{ box-sizing:border-box; cursor:zoom-out; height:100vh; overflow:hidden; position:fixed; right:0; top:0; visibility:hidden; width:100%; z-index:100000; } .wp-lightbox-overlay .close-button{ align-items:center; cursor:pointer; display:flex; justify-content:center; left:calc(env(safe-area-inset-left) + 16px); min-height:40px; min-width:40px; padding:0; position:absolute; top:calc(env(safe-area-inset-top) + 16px); z-index:5000000; } .wp-lightbox-overlay .close-button:focus,.wp-lightbox-overlay .close-button:hover,.wp-lightbox-overlay .close-button:not(:hover):not(:active):not(.has-background){ background:none; border:none; } .wp-lightbox-overlay .lightbox-image-container{ height:var(--wp--lightbox-container-height); overflow:hidden; position:absolute; right:50%; top:50%; transform:translate(50%, -50%); transform-origin:top right; width:var(--wp--lightbox-container-width); z-index:9999999999; } .wp-lightbox-overlay .wp-block-image{ align-items:center; box-sizing:border-box; display:flex; height:100%; justify-content:center; margin:0; position:relative; transform-origin:100% 0; width:100%; z-index:3000000; } .wp-lightbox-overlay .wp-block-image img{ height:var(--wp--lightbox-image-height); min-height:var(--wp--lightbox-image-height); min-width:var(--wp--lightbox-image-width); width:var(--wp--lightbox-image-width); } .wp-lightbox-overlay .wp-block-image figcaption{ display:none; } .wp-lightbox-overlay button{ background:none; border:none; } .wp-lightbox-overlay .scrim{ background-color:#fff; height:100%; opacity:.9; position:absolute; width:100%; z-index:2000000; } .wp-lightbox-overlay.active{ visibility:visible; } @media not (prefers-reduced-motion){ .wp-lightbox-overlay.active{ animation:turn-on-visibility .25s both; } .wp-lightbox-overlay.active img{ animation:turn-on-visibility .35s both; } .wp-lightbox-overlay.show-closing-animation:not(.active){ animation:turn-off-visibility .35s both; } .wp-lightbox-overlay.show-closing-animation:not(.active) img{ animation:turn-off-visibility .25s both; } .wp-lightbox-overlay.zoom.active{ animation:none; opacity:1; visibility:visible; } .wp-lightbox-overlay.zoom.active .lightbox-image-container{ animation:lightbox-zoom-in .4s; } .wp-lightbox-overlay.zoom.active .lightbox-image-container img{ animation:none; } .wp-lightbox-overlay.zoom.active .scrim{ animation:turn-on-visibility .4s forwards; } .wp-lightbox-overlay.zoom.show-closing-animation:not(.active){ animation:none; } .wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container{ animation:lightbox-zoom-out .4s; } .wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container img{ animation:none; } .wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .scrim{ animation:turn-off-visibility .4s forwards; } } @keyframes show-content-image{ 0%{ visibility:hidden; } 99%{ visibility:hidden; } to{ visibility:visible; } } @keyframes turn-on-visibility{ 0%{ opacity:0; } to{ opacity:1; } } @keyframes turn-off-visibility{ 0%{ opacity:1; visibility:visible; } 99%{ opacity:0; visibility:visible; } to{ opacity:0; visibility:hidden; } } @keyframes lightbox-zoom-in{ 0%{ transform:translate(calc(((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position))*-1), calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale)); } to{ transform:translate(50%, -50%) scale(1); } } @keyframes lightbox-zoom-out{ 0%{ transform:translate(50%, -50%) scale(1); visibility:visible; } 99%{ visibility:visible; } to{ transform:translate(calc(((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position))*-1), calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale)); visibility:hidden; } }PKt\a¶ image/editor-rtl.cssnu[.wp-block-image.wp-block-image .block-editor-media-placeholder.is-small{ min-height:60px; } figure.wp-block-image:not(.wp-block){ margin:0; } .wp-block-image{ position:relative; } .wp-block-image .is-applying img,.wp-block-image.is-transient img{ opacity:.3; } .wp-block-image figcaption img{ display:inline; } .wp-block-image .components-spinner{ margin:0; position:absolute; right:50%; top:50%; transform:translate(50%, -50%); } .wp-block-image__placeholder{ aspect-ratio:4/3; } .wp-block-image__placeholder.has-illustration:before{ background:#fff; opacity:.8; } .wp-block-image__placeholder .components-placeholder__illustration{ opacity:.1; } .wp-block-image .components-resizable-box__container{ display:table; } .wp-block-image .components-resizable-box__container img{ display:block; height:inherit; width:inherit; } .block-editor-block-list__block[data-type="core/image"] .block-editor-block-toolbar .block-editor-url-input__button-modal{ left:0; margin:-1px 0; position:absolute; right:0; } @media (min-width:600px){ .block-editor-block-list__block[data-type="core/image"] .block-editor-block-toolbar .block-editor-url-input__button-modal{ margin:-1px; } } [data-align=full]>.wp-block-image img,[data-align=wide]>.wp-block-image img{ height:auto; width:100%; } .wp-block[data-align=center]>.wp-block-image,.wp-block[data-align=left]>.wp-block-image,.wp-block[data-align=right]>.wp-block-image{ display:table; } .wp-block[data-align=center]>.wp-block-image>figcaption,.wp-block[data-align=left]>.wp-block-image>figcaption,.wp-block[data-align=right]>.wp-block-image>figcaption{ caption-side:bottom; display:table-caption; } .wp-block[data-align=left]>.wp-block-image{ margin:.5em 0 .5em 1em; } .wp-block[data-align=right]>.wp-block-image{ margin:.5em 1em .5em 0; } .wp-block[data-align=center]>.wp-block-image{ margin-left:auto; margin-right:auto; text-align:center; } .wp-block[data-align]:has(>.wp-block-image){ position:relative; } .wp-block-image__crop-area{ max-width:100%; overflow:hidden; position:relative; width:100%; } .wp-block-image__crop-area .reactEasyCrop_Container{ pointer-events:auto; } .wp-block-image__crop-area .reactEasyCrop_Container .reactEasyCrop_Image{ border:none; border-radius:0; } .wp-block-image__crop-icon{ align-items:center; display:flex; justify-content:center; min-width:48px; padding:0 8px; } .wp-block-image__crop-icon svg{ fill:currentColor; } .wp-block-image__zoom .components-popover__content{ min-width:260px; overflow:visible !important; } .wp-block-image__toolbar_content_textarea__container{ padding:8px; } .wp-block-image__toolbar_content_textarea{ width:250px; }PKt\Yimage/theme-rtl.cssnu[:root :where(.wp-block-image figcaption){ color:#555; font-size:13px; text-align:center; } .is-dark-theme :root :where(.wp-block-image figcaption){ color:#ffffffa6; } .wp-block-image{ margin:0 0 1em; }PKt\c==image/style.cssnu[.wp-block-image>a,.wp-block-image>figure>a{ display:inline-block; } .wp-block-image img{ box-sizing:border-box; height:auto; max-width:100%; vertical-align:bottom; } @media not (prefers-reduced-motion){ .wp-block-image img.hide{ visibility:hidden; } .wp-block-image img.show{ animation:show-content-image .4s; } } .wp-block-image[style*=border-radius] img,.wp-block-image[style*=border-radius]>a{ border-radius:inherit; } .wp-block-image.has-custom-border img{ box-sizing:border-box; } .wp-block-image.aligncenter{ text-align:center; } .wp-block-image.alignfull>a,.wp-block-image.alignwide>a{ width:100%; } .wp-block-image.alignfull img,.wp-block-image.alignwide img{ height:auto; width:100%; } .wp-block-image .aligncenter,.wp-block-image .alignleft,.wp-block-image .alignright,.wp-block-image.aligncenter,.wp-block-image.alignleft,.wp-block-image.alignright{ display:table; } .wp-block-image .aligncenter>figcaption,.wp-block-image .alignleft>figcaption,.wp-block-image .alignright>figcaption,.wp-block-image.aligncenter>figcaption,.wp-block-image.alignleft>figcaption,.wp-block-image.alignright>figcaption{ caption-side:bottom; display:table-caption; } .wp-block-image .alignleft{ float:left; margin:.5em 1em .5em 0; } .wp-block-image .alignright{ float:right; margin:.5em 0 .5em 1em; } .wp-block-image .aligncenter{ margin-left:auto; margin-right:auto; } .wp-block-image :where(figcaption){ margin-bottom:1em; margin-top:.5em; } .wp-block-image.is-style-circle-mask img{ border-radius:9999px; } @supports ((-webkit-mask-image:none) or (mask-image:none)) or (-webkit-mask-image:none){ .wp-block-image.is-style-circle-mask img{ border-radius:0; -webkit-mask-image:url('data:image/svg+xml;utf8,'); mask-image:url('data:image/svg+xml;utf8,'); mask-mode:alpha; -webkit-mask-position:center; mask-position:center; -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat; -webkit-mask-size:contain; mask-size:contain; } } :root :where(.wp-block-image.is-style-rounded img,.wp-block-image .is-style-rounded img){ border-radius:9999px; } .wp-block-image figure{ margin:0; } .wp-lightbox-container{ display:flex; flex-direction:column; position:relative; } .wp-lightbox-container img{ cursor:zoom-in; } .wp-lightbox-container img:hover+button{ opacity:1; } .wp-lightbox-container button{ align-items:center; -webkit-backdrop-filter:blur(16px) saturate(180%); backdrop-filter:blur(16px) saturate(180%); background-color:#5a5a5a40; border:none; border-radius:4px; cursor:zoom-in; display:flex; height:20px; justify-content:center; opacity:0; padding:0; position:absolute; right:16px; text-align:center; top:16px; width:20px; z-index:100; } @media not (prefers-reduced-motion){ .wp-lightbox-container button{ transition:opacity .2s ease; } } .wp-lightbox-container button:focus-visible{ outline:3px auto #5a5a5a40; outline:3px auto -webkit-focus-ring-color; outline-offset:3px; } .wp-lightbox-container button:hover{ cursor:pointer; opacity:1; } .wp-lightbox-container button:focus{ opacity:1; } .wp-lightbox-container button:focus,.wp-lightbox-container button:hover,.wp-lightbox-container button:not(:hover):not(:active):not(.has-background){ background-color:#5a5a5a40; border:none; } .wp-lightbox-overlay{ box-sizing:border-box; cursor:zoom-out; height:100vh; left:0; overflow:hidden; position:fixed; top:0; visibility:hidden; width:100%; z-index:100000; } .wp-lightbox-overlay .close-button{ align-items:center; cursor:pointer; display:flex; justify-content:center; min-height:40px; min-width:40px; padding:0; position:absolute; right:calc(env(safe-area-inset-right) + 16px); top:calc(env(safe-area-inset-top) + 16px); z-index:5000000; } .wp-lightbox-overlay .close-button:focus,.wp-lightbox-overlay .close-button:hover,.wp-lightbox-overlay .close-button:not(:hover):not(:active):not(.has-background){ background:none; border:none; } .wp-lightbox-overlay .lightbox-image-container{ height:var(--wp--lightbox-container-height); left:50%; overflow:hidden; position:absolute; top:50%; transform:translate(-50%, -50%); transform-origin:top left; width:var(--wp--lightbox-container-width); z-index:9999999999; } .wp-lightbox-overlay .wp-block-image{ align-items:center; box-sizing:border-box; display:flex; height:100%; justify-content:center; margin:0; position:relative; transform-origin:0 0; width:100%; z-index:3000000; } .wp-lightbox-overlay .wp-block-image img{ height:var(--wp--lightbox-image-height); min-height:var(--wp--lightbox-image-height); min-width:var(--wp--lightbox-image-width); width:var(--wp--lightbox-image-width); } .wp-lightbox-overlay .wp-block-image figcaption{ display:none; } .wp-lightbox-overlay button{ background:none; border:none; } .wp-lightbox-overlay .scrim{ background-color:#fff; height:100%; opacity:.9; position:absolute; width:100%; z-index:2000000; } .wp-lightbox-overlay.active{ visibility:visible; } @media not (prefers-reduced-motion){ .wp-lightbox-overlay.active{ animation:turn-on-visibility .25s both; } .wp-lightbox-overlay.active img{ animation:turn-on-visibility .35s both; } .wp-lightbox-overlay.show-closing-animation:not(.active){ animation:turn-off-visibility .35s both; } .wp-lightbox-overlay.show-closing-animation:not(.active) img{ animation:turn-off-visibility .25s both; } .wp-lightbox-overlay.zoom.active{ animation:none; opacity:1; visibility:visible; } .wp-lightbox-overlay.zoom.active .lightbox-image-container{ animation:lightbox-zoom-in .4s; } .wp-lightbox-overlay.zoom.active .lightbox-image-container img{ animation:none; } .wp-lightbox-overlay.zoom.active .scrim{ animation:turn-on-visibility .4s forwards; } .wp-lightbox-overlay.zoom.show-closing-animation:not(.active){ animation:none; } .wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container{ animation:lightbox-zoom-out .4s; } .wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container img{ animation:none; } .wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .scrim{ animation:turn-off-visibility .4s forwards; } } @keyframes show-content-image{ 0%{ visibility:hidden; } 99%{ visibility:hidden; } to{ visibility:visible; } } @keyframes turn-on-visibility{ 0%{ opacity:0; } to{ opacity:1; } } @keyframes turn-off-visibility{ 0%{ opacity:1; visibility:visible; } 99%{ opacity:0; visibility:visible; } to{ opacity:0; visibility:hidden; } } @keyframes lightbox-zoom-in{ 0%{ transform:translate(calc((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position)), calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale)); } to{ transform:translate(-50%, -50%) scale(1); } } @keyframes lightbox-zoom-out{ 0%{ transform:translate(-50%, -50%) scale(1); visibility:visible; } 99%{ visibility:visible; } to{ transform:translate(calc((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position)), calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale)); visibility:hidden; } }PKt\.image/theme-rtl.min.cssnu[:root :where(.wp-block-image figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme :root :where(.wp-block-image figcaption){color:#ffffffa6}.wp-block-image{margin:0 0 1em}PKt\ ++image/view.min.jsnu[import*as t from"@wordpress/interactivity";var e={d:(t,n)=>{for(var a in n)e.o(n,a)&&!e.o(t,a)&&Object.defineProperty(t,a,{enumerable:!0,get:n[a]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)};const n=(t=>{var n={};return e.d(n,t),n})({getContext:()=>t.getContext,getElement:()=>t.getElement,store:()=>t.store,withSyncEvent:()=>t.withSyncEvent});let a=!1,o=0;const{state:r,actions:i,callbacks:l}=(0,n.store)("core/image",{state:{currentImageId:null,get currentImage(){return r.metadata[r.currentImageId]},get overlayOpened(){return null!==r.currentImageId},get roleAttribute(){return r.overlayOpened?"dialog":null},get ariaModal(){return r.overlayOpened?"true":null},get enlargedSrc(){return r.currentImage.uploadedSrc||"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="},get figureStyles(){return r.overlayOpened&&`${r.currentImage.figureStyles?.replace(/margin[^;]*;?/g,"")};`},get imgStyles(){return r.overlayOpened&&`${r.currentImage.imgStyles?.replace(/;$/,"")}; object-fit:cover;`},get imageButtonRight(){const{imageId:t}=(0,n.getContext)();return r.metadata[t].imageButtonRight},get imageButtonTop(){const{imageId:t}=(0,n.getContext)();return r.metadata[t].imageButtonTop},get isContentHidden(){const t=(0,n.getContext)();return r.overlayEnabled&&r.currentImageId===t.imageId},get isContentVisible(){const t=(0,n.getContext)();return!r.overlayEnabled&&r.currentImageId===t.imageId}},actions:{showLightbox(){const{imageId:t}=(0,n.getContext)();r.metadata[t].imageRef?.complete&&(r.scrollTopReset=document.documentElement.scrollTop,r.scrollLeftReset=document.documentElement.scrollLeft,r.overlayEnabled=!0,r.currentImageId=t,l.setOverlayStyles())},hideLightbox(){r.overlayEnabled&&(r.showClosingAnimation=!0,r.overlayEnabled=!1,setTimeout((function(){r.currentImage.buttonRef.focus({preventScroll:!0}),r.currentImageId=null}),450))},handleKeydown:(0,n.withSyncEvent)((t=>{if(r.overlayEnabled){if("Tab"===t.key){t.preventDefault();const{ref:e}=(0,n.getElement)();e.querySelector("button").focus()}"Escape"===t.key&&i.hideLightbox()}})),handleTouchMove:(0,n.withSyncEvent)((t=>{r.overlayEnabled&&t.preventDefault()})),handleTouchStart(){a=!0},handleTouchEnd(){o=Date.now(),a=!1},handleScroll(){r.overlayOpened&&!a&&Date.now()-o>450&&window.scrollTo(r.scrollLeftReset,r.scrollTopReset)}},callbacks:{setOverlayStyles(){if(!r.overlayEnabled)return;let{naturalWidth:t,naturalHeight:e,offsetWidth:n,offsetHeight:a}=r.currentImage.imageRef,{x:o,y:i}=r.currentImage.imageRef.getBoundingClientRect();const l=t/e;let g=n/a;if("contain"===r.currentImage.scaleAttr)if(l>g){const t=n/l;i+=(a-t)/2,a=t}else{const t=a*l;o+=(n-t)/2,n=t}g=n/a;let c=parseFloat("none"!==r.currentImage.targetWidth?r.currentImage.targetWidth:t),s=parseFloat("none"!==r.currentImage.targetHeight?r.currentImage.targetHeight:e),d=c/s,u=c,m=s,h=c,p=s;if(l.toFixed(2)!==d.toFixed(2)){if(l>d){const t=c/l;s-t>c?(s=t,c=t*l):s=c/l}else{const t=s*l;c-t>s?(c=t,s=t/l):c=s*l}h=c,p=s,d=c/s,g>d?(u=c,m=u/g):(m=s,u=m*g)}(n>h||a>p)&&(h=n,p=a);let f=0;window.innerWidth>480?f=80:window.innerWidth>1920&&(f=160);const y=Math.min(window.innerWidth-f,h),w=Math.min(window.innerHeight-80,p);g>y/w?(h=y,p=h/g):(p=w,h=p*g);const b=n/h,I=c*(h/u),v=s*(p/m);r.overlayStyles=`\n\t\t\t\t\t--wp--lightbox-initial-top-position: ${i}px;\n\t\t\t\t\t--wp--lightbox-initial-left-position: ${o}px;\n\t\t\t\t\t--wp--lightbox-container-width: ${h+1}px;\n\t\t\t\t\t--wp--lightbox-container-height: ${p+1}px;\n\t\t\t\t\t--wp--lightbox-image-width: ${I}px;\n\t\t\t\t\t--wp--lightbox-image-height: ${v}px;\n\t\t\t\t\t--wp--lightbox-scale: ${b};\n\t\t\t\t\t--wp--lightbox-scrollbar-width: ${window.innerWidth-document.documentElement.clientWidth}px;\n\t\t\t\t`},setButtonStyles(){const{imageId:t}=(0,n.getContext)(),{ref:e}=(0,n.getElement)();r.metadata[t].imageRef=e,r.metadata[t].currentSrc=e.currentSrc;const{naturalWidth:a,naturalHeight:o,offsetWidth:i,offsetHeight:l}=e;if(0===a||0===o)return;const g=e.parentElement,c=e.parentElement.clientWidth;let s=e.parentElement.clientHeight;const d=g.querySelector("figcaption");if(d){const t=window.getComputedStyle(d);["absolute","fixed"].includes(t.position)||(s=s-d.offsetHeight-parseFloat(t.marginTop)-parseFloat(t.marginBottom))}const u=s-l,m=c-i;let h=u+16,p=m+16;if("contain"===r.metadata[t].scaleAttr){const t=a/o;if(t>=i/l){h=(l-i/t)/2+u+16,p=m+16}else{h=u+16,p=(i-l*t)/2+m+16}}r.metadata[t].imageButtonTop=h,r.metadata[t].imageButtonRight=p},setOverlayFocus(){if(r.overlayEnabled){const{ref:t}=(0,n.getElement)();t.focus()}},initTriggerButton(){const{imageId:t}=(0,n.getContext)(),{ref:e}=(0,n.getElement)();r.metadata[t].buttonRef=e}}},{lock:!0});PKt\image/style-rtl.min.cssnu[.wp-block-image>a,.wp-block-image>figure>a{display:inline-block}.wp-block-image img{box-sizing:border-box;height:auto;max-width:100%;vertical-align:bottom}@media not (prefers-reduced-motion){.wp-block-image img.hide{visibility:hidden}.wp-block-image img.show{animation:show-content-image .4s}}.wp-block-image[style*=border-radius] img,.wp-block-image[style*=border-radius]>a{border-radius:inherit}.wp-block-image.has-custom-border img{box-sizing:border-box}.wp-block-image.aligncenter{text-align:center}.wp-block-image.alignfull>a,.wp-block-image.alignwide>a{width:100%}.wp-block-image.alignfull img,.wp-block-image.alignwide img{height:auto;width:100%}.wp-block-image .aligncenter,.wp-block-image .alignleft,.wp-block-image .alignright,.wp-block-image.aligncenter,.wp-block-image.alignleft,.wp-block-image.alignright{display:table}.wp-block-image .aligncenter>figcaption,.wp-block-image .alignleft>figcaption,.wp-block-image .alignright>figcaption,.wp-block-image.aligncenter>figcaption,.wp-block-image.alignleft>figcaption,.wp-block-image.alignright>figcaption{caption-side:bottom;display:table-caption}.wp-block-image .alignleft{float:left;margin:.5em 1em .5em 0}.wp-block-image .alignright{float:right;margin:.5em 0 .5em 1em}.wp-block-image .aligncenter{margin-left:auto;margin-right:auto}.wp-block-image :where(figcaption){margin-bottom:1em;margin-top:.5em}.wp-block-image.is-style-circle-mask img{border-radius:9999px}@supports ((-webkit-mask-image:none) or (mask-image:none)) or (-webkit-mask-image:none){.wp-block-image.is-style-circle-mask img{border-radius:0;-webkit-mask-image:url('data:image/svg+xml;utf8,');mask-image:url('data:image/svg+xml;utf8,');mask-mode:alpha;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}}:root :where(.wp-block-image.is-style-rounded img,.wp-block-image .is-style-rounded img){border-radius:9999px}.wp-block-image figure{margin:0}.wp-lightbox-container{display:flex;flex-direction:column;position:relative}.wp-lightbox-container img{cursor:zoom-in}.wp-lightbox-container img:hover+button{opacity:1}.wp-lightbox-container button{align-items:center;-webkit-backdrop-filter:blur(16px) saturate(180%);backdrop-filter:blur(16px) saturate(180%);background-color:#5a5a5a40;border:none;border-radius:4px;cursor:zoom-in;display:flex;height:20px;justify-content:center;left:16px;opacity:0;padding:0;position:absolute;text-align:center;top:16px;width:20px;z-index:100}@media not (prefers-reduced-motion){.wp-lightbox-container button{transition:opacity .2s ease}}.wp-lightbox-container button:focus-visible{outline:3px auto #5a5a5a40;outline:3px auto -webkit-focus-ring-color;outline-offset:3px}.wp-lightbox-container button:hover{cursor:pointer;opacity:1}.wp-lightbox-container button:focus{opacity:1}.wp-lightbox-container button:focus,.wp-lightbox-container button:hover,.wp-lightbox-container button:not(:hover):not(:active):not(.has-background){background-color:#5a5a5a40;border:none}.wp-lightbox-overlay{box-sizing:border-box;cursor:zoom-out;height:100vh;overflow:hidden;position:fixed;right:0;top:0;visibility:hidden;width:100%;z-index:100000}.wp-lightbox-overlay .close-button{align-items:center;cursor:pointer;display:flex;justify-content:center;left:calc(env(safe-area-inset-left) + 16px);min-height:40px;min-width:40px;padding:0;position:absolute;top:calc(env(safe-area-inset-top) + 16px);z-index:5000000}.wp-lightbox-overlay .close-button:focus,.wp-lightbox-overlay .close-button:hover,.wp-lightbox-overlay .close-button:not(:hover):not(:active):not(.has-background){background:none;border:none}.wp-lightbox-overlay .lightbox-image-container{height:var(--wp--lightbox-container-height);overflow:hidden;position:absolute;right:50%;top:50%;transform:translate(50%,-50%);transform-origin:top right;width:var(--wp--lightbox-container-width);z-index:9999999999}.wp-lightbox-overlay .wp-block-image{align-items:center;box-sizing:border-box;display:flex;height:100%;justify-content:center;margin:0;position:relative;transform-origin:100% 0;width:100%;z-index:3000000}.wp-lightbox-overlay .wp-block-image img{height:var(--wp--lightbox-image-height);min-height:var(--wp--lightbox-image-height);min-width:var(--wp--lightbox-image-width);width:var(--wp--lightbox-image-width)}.wp-lightbox-overlay .wp-block-image figcaption{display:none}.wp-lightbox-overlay button{background:none;border:none}.wp-lightbox-overlay .scrim{background-color:#fff;height:100%;opacity:.9;position:absolute;width:100%;z-index:2000000}.wp-lightbox-overlay.active{visibility:visible}@media not (prefers-reduced-motion){.wp-lightbox-overlay.active{animation:turn-on-visibility .25s both}.wp-lightbox-overlay.active img{animation:turn-on-visibility .35s both}.wp-lightbox-overlay.show-closing-animation:not(.active){animation:turn-off-visibility .35s both}.wp-lightbox-overlay.show-closing-animation:not(.active) img{animation:turn-off-visibility .25s both}.wp-lightbox-overlay.zoom.active{animation:none;opacity:1;visibility:visible}.wp-lightbox-overlay.zoom.active .lightbox-image-container{animation:lightbox-zoom-in .4s}.wp-lightbox-overlay.zoom.active .lightbox-image-container img{animation:none}.wp-lightbox-overlay.zoom.active .scrim{animation:turn-on-visibility .4s forwards}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active){animation:none}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container{animation:lightbox-zoom-out .4s}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container img{animation:none}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .scrim{animation:turn-off-visibility .4s forwards}}@keyframes show-content-image{0%{visibility:hidden}99%{visibility:hidden}to{visibility:visible}}@keyframes turn-on-visibility{0%{opacity:0}to{opacity:1}}@keyframes turn-off-visibility{0%{opacity:1;visibility:visible}99%{opacity:0;visibility:visible}to{opacity:0;visibility:hidden}}@keyframes lightbox-zoom-in{0%{transform:translate(calc(((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position))*-1),calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale))}to{transform:translate(50%,-50%) scale(1)}}@keyframes lightbox-zoom-out{0%{transform:translate(50%,-50%) scale(1);visibility:visible}99%{visibility:visible}to{transform:translate(calc(((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position))*-1),calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale));visibility:hidden}}PKt\u image/editor-rtl.min.cssnu[.wp-block-image.wp-block-image .block-editor-media-placeholder.is-small{min-height:60px}figure.wp-block-image:not(.wp-block){margin:0}.wp-block-image{position:relative}.wp-block-image .is-applying img,.wp-block-image.is-transient img{opacity:.3}.wp-block-image figcaption img{display:inline}.wp-block-image .components-spinner{margin:0;position:absolute;right:50%;top:50%;transform:translate(50%,-50%)}.wp-block-image__placeholder{aspect-ratio:4/3}.wp-block-image__placeholder.has-illustration:before{background:#fff;opacity:.8}.wp-block-image__placeholder .components-placeholder__illustration{opacity:.1}.wp-block-image .components-resizable-box__container{display:table}.wp-block-image .components-resizable-box__container img{display:block;height:inherit;width:inherit}.block-editor-block-list__block[data-type="core/image"] .block-editor-block-toolbar .block-editor-url-input__button-modal{left:0;margin:-1px 0;position:absolute;right:0}@media (min-width:600px){.block-editor-block-list__block[data-type="core/image"] .block-editor-block-toolbar .block-editor-url-input__button-modal{margin:-1px}}[data-align=full]>.wp-block-image img,[data-align=wide]>.wp-block-image img{height:auto;width:100%}.wp-block[data-align=center]>.wp-block-image,.wp-block[data-align=left]>.wp-block-image,.wp-block[data-align=right]>.wp-block-image{display:table}.wp-block[data-align=center]>.wp-block-image>figcaption,.wp-block[data-align=left]>.wp-block-image>figcaption,.wp-block[data-align=right]>.wp-block-image>figcaption{caption-side:bottom;display:table-caption}.wp-block[data-align=left]>.wp-block-image{margin:.5em 0 .5em 1em}.wp-block[data-align=right]>.wp-block-image{margin:.5em 1em .5em 0}.wp-block[data-align=center]>.wp-block-image{margin-left:auto;margin-right:auto;text-align:center}.wp-block[data-align]:has(>.wp-block-image){position:relative}.wp-block-image__crop-area{max-width:100%;overflow:hidden;position:relative;width:100%}.wp-block-image__crop-area .reactEasyCrop_Container{pointer-events:auto}.wp-block-image__crop-area .reactEasyCrop_Container .reactEasyCrop_Image{border:none;border-radius:0}.wp-block-image__crop-icon{align-items:center;display:flex;justify-content:center;min-width:48px;padding:0 8px}.wp-block-image__crop-icon svg{fill:currentColor}.wp-block-image__zoom .components-popover__content{min-width:260px;overflow:visible!important}.wp-block-image__toolbar_content_textarea__container{padding:8px}.wp-block-image__toolbar_content_textarea{width:250px}PKt\a image/editor.cssnu[.wp-block-image.wp-block-image .block-editor-media-placeholder.is-small{ min-height:60px; } figure.wp-block-image:not(.wp-block){ margin:0; } .wp-block-image{ position:relative; } .wp-block-image .is-applying img,.wp-block-image.is-transient img{ opacity:.3; } .wp-block-image figcaption img{ display:inline; } .wp-block-image .components-spinner{ left:50%; margin:0; position:absolute; top:50%; transform:translate(-50%, -50%); } .wp-block-image__placeholder{ aspect-ratio:4/3; } .wp-block-image__placeholder.has-illustration:before{ background:#fff; opacity:.8; } .wp-block-image__placeholder .components-placeholder__illustration{ opacity:.1; } .wp-block-image .components-resizable-box__container{ display:table; } .wp-block-image .components-resizable-box__container img{ display:block; height:inherit; width:inherit; } .block-editor-block-list__block[data-type="core/image"] .block-editor-block-toolbar .block-editor-url-input__button-modal{ left:0; margin:-1px 0; position:absolute; right:0; } @media (min-width:600px){ .block-editor-block-list__block[data-type="core/image"] .block-editor-block-toolbar .block-editor-url-input__button-modal{ margin:-1px; } } [data-align=full]>.wp-block-image img,[data-align=wide]>.wp-block-image img{ height:auto; width:100%; } .wp-block[data-align=center]>.wp-block-image,.wp-block[data-align=left]>.wp-block-image,.wp-block[data-align=right]>.wp-block-image{ display:table; } .wp-block[data-align=center]>.wp-block-image>figcaption,.wp-block[data-align=left]>.wp-block-image>figcaption,.wp-block[data-align=right]>.wp-block-image>figcaption{ caption-side:bottom; display:table-caption; } .wp-block[data-align=left]>.wp-block-image{ margin:.5em 1em .5em 0; } .wp-block[data-align=right]>.wp-block-image{ margin:.5em 0 .5em 1em; } .wp-block[data-align=center]>.wp-block-image{ margin-left:auto; margin-right:auto; text-align:center; } .wp-block[data-align]:has(>.wp-block-image){ position:relative; } .wp-block-image__crop-area{ max-width:100%; overflow:hidden; position:relative; width:100%; } .wp-block-image__crop-area .reactEasyCrop_Container{ pointer-events:auto; } .wp-block-image__crop-area .reactEasyCrop_Container .reactEasyCrop_Image{ border:none; border-radius:0; } .wp-block-image__crop-icon{ align-items:center; display:flex; justify-content:center; min-width:48px; padding:0 8px; } .wp-block-image__crop-icon svg{ fill:currentColor; } .wp-block-image__zoom .components-popover__content{ min-width:260px; overflow:visible !important; } .wp-block-image__toolbar_content_textarea__container{ padding:8px; } .wp-block-image__toolbar_content_textarea{ width:250px; }PKt\TJ query-total.phpnu[context['query']['inherit'] ) && $block->context['query']['inherit'] ) { $query_to_use = $wp_query; $current_page = max( 1, (int) get_query_var( 'paged', 1 ) ); } else { $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; $current_page = isset( $_GET[ $page_key ] ) ? (int) $_GET[ $page_key ] : 1; $query_to_use = new WP_Query( build_query_vars_from_query_block( $block, $current_page ) ); } $max_rows = $query_to_use->found_posts; $posts_per_page = (int) $query_to_use->get( 'posts_per_page' ); // Calculate the range of posts being displayed. $start = ( 0 === $max_rows ) ? 0 : ( ( $current_page - 1 ) * $posts_per_page + 1 ); $end = min( $start + $posts_per_page - 1, $max_rows ); // Prepare the display based on the `displayType` attribute. $output = ''; switch ( $attributes['displayType'] ) { case 'range-display': if ( $start === $end ) { $output = sprintf( /* translators: 1: Start index of posts, 2: Total number of posts */ __( 'Displaying %1$s of %2$s' ), $start, $max_rows ); } else { $output = sprintf( /* translators: 1: Start index of posts, 2: End index of posts, 3: Total number of posts */ __( 'Displaying %1$s – %2$s of %3$s' ), $start, $end, $max_rows ); } break; case 'total-results': default: // translators: %d: number of results. $output = sprintf( _n( '%d result found', '%d results found', $max_rows ), $max_rows ); break; } return sprintf( '
                %2$s
                ', $wrapper_attributes, $output ); } /** * Registers the `query-total` block. * * @since 6.8.0 */ function register_block_core_query_total() { register_block_type_from_metadata( __DIR__ . '/query-total', array( 'render_callback' => 'render_block_core_query_total', ) ); } add_action( 'init', 'register_block_core_query_total' ); PKt\  gallery/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/gallery", "title": "Gallery", "category": "media", "allowedBlocks": [ "core/image" ], "description": "Display multiple images in a rich gallery.", "keywords": [ "images", "photos" ], "textdomain": "default", "attributes": { "images": { "type": "array", "default": [], "source": "query", "selector": ".blocks-gallery-item", "query": { "url": { "type": "string", "source": "attribute", "selector": "img", "attribute": "src" }, "fullUrl": { "type": "string", "source": "attribute", "selector": "img", "attribute": "data-full-url" }, "link": { "type": "string", "source": "attribute", "selector": "img", "attribute": "data-link" }, "alt": { "type": "string", "source": "attribute", "selector": "img", "attribute": "alt", "default": "" }, "id": { "type": "string", "source": "attribute", "selector": "img", "attribute": "data-id" }, "caption": { "type": "rich-text", "source": "rich-text", "selector": ".blocks-gallery-item__caption" } } }, "ids": { "type": "array", "items": { "type": "number" }, "default": [] }, "shortCodeTransforms": { "type": "array", "items": { "type": "object" }, "default": [] }, "columns": { "type": "number", "minimum": 1, "maximum": 8 }, "caption": { "type": "rich-text", "source": "rich-text", "selector": ".blocks-gallery-caption" }, "imageCrop": { "type": "boolean", "default": true }, "randomOrder": { "type": "boolean", "default": false }, "fixedHeight": { "type": "boolean", "default": true }, "linkTarget": { "type": "string" }, "linkTo": { "type": "string" }, "sizeSlug": { "type": "string", "default": "large" }, "allowResize": { "type": "boolean", "default": false } }, "providesContext": { "allowResize": "allowResize", "imageCrop": "imageCrop", "fixedHeight": "fixedHeight" }, "supports": { "anchor": true, "align": true, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "color": true, "radius": true } }, "html": false, "units": [ "px", "em", "rem", "vh", "vw" ], "spacing": { "margin": true, "padding": true, "blockGap": [ "horizontal", "vertical" ], "__experimentalSkipSerialization": [ "blockGap" ], "__experimentalDefaultControls": { "blockGap": true, "margin": false, "padding": false } }, "color": { "text": false, "background": true, "gradients": true }, "layout": { "allowSwitching": false, "allowInheriting": false, "allowEditing": false, "default": { "type": "flex" } }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-gallery-editor", "style": "wp-block-gallery" } PKt\rɓgallery/editor.min.cssnu[:root :where(figure.wp-block-gallery){display:block}:root :where(figure.wp-block-gallery)>.blocks-gallery-caption{flex:0 0 100%}:root :where(figure.wp-block-gallery)>.blocks-gallery-media-placeholder-wrapper{flex-basis:100%}:root :where(figure.wp-block-gallery) .wp-block-image .components-notice.is-error{display:block}:root :where(figure.wp-block-gallery) .wp-block-image .components-notice__content{margin:4px 0}:root :where(figure.wp-block-gallery) .wp-block-image .components-notice__dismiss{position:absolute;right:5px;top:0}:root :where(figure.wp-block-gallery) .block-editor-media-placeholder.is-appender .components-placeholder__label{display:none}:root :where(figure.wp-block-gallery) .block-editor-media-placeholder.is-appender .block-editor-media-placeholder__button{margin-bottom:0}:root :where(figure.wp-block-gallery) .block-editor-media-placeholder{margin:0}:root :where(figure.wp-block-gallery) .block-editor-media-placeholder .components-placeholder__label{display:flex}:root :where(figure.wp-block-gallery) .block-editor-media-placeholder figcaption{z-index:2}:root :where(figure.wp-block-gallery) .components-spinner{left:50%;margin-left:-9px;margin-top:-9px;position:absolute;top:50%}.gallery-settings-buttons .components-button:first-child{margin-right:8px}.gallery-image-sizes .components-base-control__label{margin-bottom:4px}.gallery-image-sizes .gallery-image-sizes__loading{align-items:center;color:#757575;display:flex;font-size:12px}.gallery-image-sizes .components-spinner{margin:0 8px 0 4px}.blocks-gallery-item figure:not(.is-selected):focus,.blocks-gallery-item img:focus{outline:none}.blocks-gallery-item figure.is-selected:before{bottom:0;box-shadow:0 0 0 1px #fff inset,0 0 0 3px var(--wp-admin-theme-color) inset;content:"";left:0;outline:2px solid #0000;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.blocks-gallery-item figure.is-transient img{opacity:.3}.blocks-gallery-item .block-editor-media-placeholder{height:100%;margin:0}.blocks-gallery-item .block-editor-media-placeholder .components-placeholder__label{display:flex}.wp-block-gallery ul.blocks-gallery-grid{margin:0;padding:0}@media (min-width:600px){.wp-block-update-gallery-modal{max-width:480px}}.wp-block-update-gallery-modal-buttons{display:flex;gap:12px;justify-content:flex-end}PKt\ 3{{gallery/theme.min.cssnu[.blocks-gallery-caption{color:#555;font-size:13px;text-align:center}.is-dark-theme .blocks-gallery-caption{color:#ffffffa6}PKt\#digallery/theme.cssnu[.blocks-gallery-caption{ color:#555; font-size:13px; text-align:center; } .is-dark-theme .blocks-gallery-caption{ color:#ffffffa6; }PKt\O'7>7>gallery/style.min.cssnu[.blocks-gallery-grid:not(.has-nested-images),.wp-block-gallery:not(.has-nested-images){display:flex;flex-wrap:wrap;list-style-type:none;margin:0;padding:0}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item{display:flex;flex-direction:column;flex-grow:1;justify-content:center;margin:0 1em 1em 0;position:relative;width:calc(50% - 1em)}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item:nth-of-type(2n){margin-right:0}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figure,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figure,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figure,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figure{align-items:flex-end;display:flex;height:100%;justify-content:flex-start;margin:0}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image img,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item img{display:block;height:auto;max-width:100%;width:auto}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figcaption,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figcaption,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figcaption,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figcaption{background:linear-gradient(0deg,#000000b3,#0000004d 70%,#0000);bottom:0;box-sizing:border-box;color:#fff;font-size:.8em;margin:0;max-height:100%;overflow:auto;padding:3em .77em .7em;position:absolute;text-align:center;width:100%;z-index:2}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figcaption img,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figcaption img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figcaption img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figcaption img{display:inline}.blocks-gallery-grid:not(.has-nested-images) figcaption,.wp-block-gallery:not(.has-nested-images) figcaption{flex-grow:1}.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-image a,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-image img,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-item a,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-item img,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-image a,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-image img,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-item a,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-item img{flex:1;height:100%;object-fit:cover;width:100%}.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-item{margin-right:0;width:100%}@media (min-width:600px){.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-item{margin-right:1em;width:calc(33.33333% - .66667em)}.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-item{margin-right:1em;width:calc(25% - .75em)}.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-item{margin-right:1em;width:calc(20% - .8em)}.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-item{margin-right:1em;width:calc(16.66667% - .83333em)}.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-item{margin-right:1em;width:calc(14.28571% - .85714em)}.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-item{margin-right:1em;width:calc(12.5% - .875em)}.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-image:nth-of-type(1n),.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-item:nth-of-type(1n),.blocks-gallery-grid:not(.has-nested-images).columns-2 .blocks-gallery-image:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images).columns-2 .blocks-gallery-item:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-image:nth-of-type(3n),.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-item:nth-of-type(3n),.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-image:nth-of-type(4n),.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-item:nth-of-type(4n),.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-image:nth-of-type(5n),.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-item:nth-of-type(5n),.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-image:nth-of-type(6n),.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-item:nth-of-type(6n),.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-image:nth-of-type(7n),.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-item:nth-of-type(7n),.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-image:nth-of-type(8n),.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-item:nth-of-type(8n),.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-image:nth-of-type(1n),.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-item:nth-of-type(1n),.wp-block-gallery:not(.has-nested-images).columns-2 .blocks-gallery-image:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images).columns-2 .blocks-gallery-item:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-image:nth-of-type(3n),.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-item:nth-of-type(3n),.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-image:nth-of-type(4n),.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-item:nth-of-type(4n),.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-image:nth-of-type(5n),.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-item:nth-of-type(5n),.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-image:nth-of-type(6n),.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-item:nth-of-type(6n),.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-image:nth-of-type(7n),.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-item:nth-of-type(7n),.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-image:nth-of-type(8n),.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-item:nth-of-type(8n){margin-right:0}}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image:last-child,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item:last-child,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image:last-child,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item:last-child{margin-right:0}.blocks-gallery-grid:not(.has-nested-images).alignleft,.blocks-gallery-grid:not(.has-nested-images).alignright,.wp-block-gallery:not(.has-nested-images).alignleft,.wp-block-gallery:not(.has-nested-images).alignright{max-width:420px;width:100%}.blocks-gallery-grid:not(.has-nested-images).aligncenter .blocks-gallery-item figure,.wp-block-gallery:not(.has-nested-images).aligncenter .blocks-gallery-item figure{justify-content:center}.wp-block-gallery:not(.is-cropped) .blocks-gallery-item{align-self:flex-start}figure.wp-block-gallery.has-nested-images{align-items:normal}.wp-block-gallery.has-nested-images figure.wp-block-image:not(#individual-image){margin:0;width:calc(50% - var(--wp--style--unstable-gallery-gap, 16px)/2)}.wp-block-gallery.has-nested-images figure.wp-block-image{box-sizing:border-box;display:flex;flex-direction:column;flex-grow:1;justify-content:center;max-width:100%;position:relative}.wp-block-gallery.has-nested-images figure.wp-block-image>a,.wp-block-gallery.has-nested-images figure.wp-block-image>div{flex-direction:column;flex-grow:1;margin:0}.wp-block-gallery.has-nested-images figure.wp-block-image img{display:block;height:auto;max-width:100%!important;width:auto}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption,.wp-block-gallery.has-nested-images figure.wp-block-image:has(figcaption):before{bottom:0;left:0;max-height:100%;position:absolute;right:0}.wp-block-gallery.has-nested-images figure.wp-block-image:has(figcaption):before{-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);content:"";height:100%;-webkit-mask-image:linear-gradient(0deg,#000 20%,#0000);mask-image:linear-gradient(0deg,#000 20%,#0000);max-height:40%}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption{background:linear-gradient(0deg,#0006,#0000);box-sizing:border-box;color:#fff;font-size:13px;margin:0;overflow:auto;padding:1em;scrollbar-color:#0000 #0000;scrollbar-gutter:stable both-edges;scrollbar-width:thin;text-align:center;text-shadow:0 0 1.5px #000;will-change:transform}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar{height:12px;width:12px}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar-track{background-color:initial}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar-thumb{background-clip:padding-box;background-color:initial;border:3px solid #0000;border-radius:8px}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus-within::-webkit-scrollbar-thumb,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus::-webkit-scrollbar-thumb,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:hover::-webkit-scrollbar-thumb{background-color:#fffc}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus-within,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:hover{scrollbar-color:#fffc #0000}@media (hover:none){.wp-block-gallery.has-nested-images figure.wp-block-image figcaption{scrollbar-color:#fffc #0000}}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption img{display:inline}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption a{color:inherit}.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border img{box-sizing:border-box}.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border>a,.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border>div,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded>a,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded>div{flex:1 1 auto}.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border figcaption,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded figcaption{background:none;color:inherit;flex:initial;margin:0;padding:10px 10px 9px;position:relative;text-shadow:none}.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border:before,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded:before{content:none}.wp-block-gallery.has-nested-images figcaption{flex-basis:100%;flex-grow:1;text-align:center}.wp-block-gallery.has-nested-images:not(.is-cropped) figure.wp-block-image:not(#individual-image){margin-bottom:auto;margin-top:0}.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image){align-self:inherit}.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image)>a,.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image)>div:not(.components-drop-zone){display:flex}.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image) a,.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image) img{flex:1 0 0%;height:100%;object-fit:cover;width:100%}.wp-block-gallery.has-nested-images.columns-1 figure.wp-block-image:not(#individual-image){width:100%}@media (min-width:600px){.wp-block-gallery.has-nested-images.columns-3 figure.wp-block-image:not(#individual-image){width:calc(33.33333% - var(--wp--style--unstable-gallery-gap, 16px)*.66667)}.wp-block-gallery.has-nested-images.columns-4 figure.wp-block-image:not(#individual-image){width:calc(25% - var(--wp--style--unstable-gallery-gap, 16px)*.75)}.wp-block-gallery.has-nested-images.columns-5 figure.wp-block-image:not(#individual-image){width:calc(20% - var(--wp--style--unstable-gallery-gap, 16px)*.8)}.wp-block-gallery.has-nested-images.columns-6 figure.wp-block-image:not(#individual-image){width:calc(16.66667% - var(--wp--style--unstable-gallery-gap, 16px)*.83333)}.wp-block-gallery.has-nested-images.columns-7 figure.wp-block-image:not(#individual-image){width:calc(14.28571% - var(--wp--style--unstable-gallery-gap, 16px)*.85714)}.wp-block-gallery.has-nested-images.columns-8 figure.wp-block-image:not(#individual-image){width:calc(12.5% - var(--wp--style--unstable-gallery-gap, 16px)*.875)}.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image){width:calc(33.33% - var(--wp--style--unstable-gallery-gap, 16px)*.66667)}.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2),.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2)~figure.wp-block-image:not(#individual-image){width:calc(50% - var(--wp--style--unstable-gallery-gap, 16px)*.5)}.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:last-child{width:100%}}.wp-block-gallery.has-nested-images.alignleft,.wp-block-gallery.has-nested-images.alignright{max-width:420px;width:100%}.wp-block-gallery.has-nested-images.aligncenter{justify-content:center}PKt\;.A.Agallery/style-rtl.cssnu[.blocks-gallery-grid:not(.has-nested-images),.wp-block-gallery:not(.has-nested-images){ display:flex; flex-wrap:wrap; list-style-type:none; margin:0; padding:0; } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item{ display:flex; flex-direction:column; flex-grow:1; justify-content:center; margin:0 0 1em 1em; position:relative; width:calc(50% - 1em); } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item:nth-of-type(2n){ margin-left:0; } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figure,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figure,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figure,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figure{ align-items:flex-end; display:flex; height:100%; justify-content:flex-start; margin:0; } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image img,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item img{ display:block; height:auto; max-width:100%; width:auto; } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figcaption,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figcaption,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figcaption,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figcaption{ background:linear-gradient(0deg, #000000b3, #0000004d 70%, #0000); bottom:0; box-sizing:border-box; color:#fff; font-size:.8em; margin:0; max-height:100%; overflow:auto; padding:3em .77em .7em; position:absolute; text-align:center; width:100%; z-index:2; } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figcaption img,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figcaption img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figcaption img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figcaption img{ display:inline; } .blocks-gallery-grid:not(.has-nested-images) figcaption,.wp-block-gallery:not(.has-nested-images) figcaption{ flex-grow:1; } .blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-image a,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-image img,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-item a,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-item img,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-image a,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-image img,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-item a,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-item img{ flex:1; height:100%; object-fit:cover; width:100%; } .blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-item{ margin-left:0; width:100%; } @media (min-width:600px){ .blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-item{ margin-left:1em; width:calc(33.33333% - .66667em); } .blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-item{ margin-left:1em; width:calc(25% - .75em); } .blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-item{ margin-left:1em; width:calc(20% - .8em); } .blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-item{ margin-left:1em; width:calc(16.66667% - .83333em); } .blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-item{ margin-left:1em; width:calc(14.28571% - .85714em); } .blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-item{ margin-left:1em; width:calc(12.5% - .875em); } .blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-image:nth-of-type(1n),.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-item:nth-of-type(1n),.blocks-gallery-grid:not(.has-nested-images).columns-2 .blocks-gallery-image:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images).columns-2 .blocks-gallery-item:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-image:nth-of-type(3n),.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-item:nth-of-type(3n),.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-image:nth-of-type(4n),.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-item:nth-of-type(4n),.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-image:nth-of-type(5n),.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-item:nth-of-type(5n),.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-image:nth-of-type(6n),.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-item:nth-of-type(6n),.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-image:nth-of-type(7n),.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-item:nth-of-type(7n),.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-image:nth-of-type(8n),.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-item:nth-of-type(8n),.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-image:nth-of-type(1n),.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-item:nth-of-type(1n),.wp-block-gallery:not(.has-nested-images).columns-2 .blocks-gallery-image:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images).columns-2 .blocks-gallery-item:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-image:nth-of-type(3n),.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-item:nth-of-type(3n),.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-image:nth-of-type(4n),.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-item:nth-of-type(4n),.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-image:nth-of-type(5n),.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-item:nth-of-type(5n),.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-image:nth-of-type(6n),.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-item:nth-of-type(6n),.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-image:nth-of-type(7n),.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-item:nth-of-type(7n),.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-image:nth-of-type(8n),.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-item:nth-of-type(8n){ margin-left:0; } } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image:last-child,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item:last-child,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image:last-child,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item:last-child{ margin-left:0; } .blocks-gallery-grid:not(.has-nested-images).alignleft,.blocks-gallery-grid:not(.has-nested-images).alignright,.wp-block-gallery:not(.has-nested-images).alignleft,.wp-block-gallery:not(.has-nested-images).alignright{ max-width:420px; width:100%; } .blocks-gallery-grid:not(.has-nested-images).aligncenter .blocks-gallery-item figure,.wp-block-gallery:not(.has-nested-images).aligncenter .blocks-gallery-item figure{ justify-content:center; } .wp-block-gallery:not(.is-cropped) .blocks-gallery-item{ align-self:flex-start; } figure.wp-block-gallery.has-nested-images{ align-items:normal; } .wp-block-gallery.has-nested-images figure.wp-block-image:not(#individual-image){ margin:0; width:calc(50% - var(--wp--style--unstable-gallery-gap, 16px)/2); } .wp-block-gallery.has-nested-images figure.wp-block-image{ box-sizing:border-box; display:flex; flex-direction:column; flex-grow:1; justify-content:center; max-width:100%; position:relative; } .wp-block-gallery.has-nested-images figure.wp-block-image>a,.wp-block-gallery.has-nested-images figure.wp-block-image>div{ flex-direction:column; flex-grow:1; margin:0; } .wp-block-gallery.has-nested-images figure.wp-block-image img{ display:block; height:auto; max-width:100% !important; width:auto; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption,.wp-block-gallery.has-nested-images figure.wp-block-image:has(figcaption):before{ bottom:0; left:0; max-height:100%; position:absolute; right:0; } .wp-block-gallery.has-nested-images figure.wp-block-image:has(figcaption):before{ -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px); content:""; height:100%; -webkit-mask-image:linear-gradient(0deg, #000 20%, #0000); mask-image:linear-gradient(0deg, #000 20%, #0000); max-height:40%; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption{ background:linear-gradient(0deg, #0006, #0000); box-sizing:border-box; color:#fff; font-size:13px; margin:0; overflow:auto; padding:1em; scrollbar-color:#0000 #0000; scrollbar-gutter:stable both-edges; scrollbar-width:thin; text-align:center; text-shadow:0 0 1.5px #000; will-change:transform; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar{ height:12px; width:12px; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar-track{ background-color:initial; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar-thumb{ background-clip:padding-box; background-color:initial; border:3px solid #0000; border-radius:8px; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus-within::-webkit-scrollbar-thumb,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus::-webkit-scrollbar-thumb,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:hover::-webkit-scrollbar-thumb{ background-color:#fffc; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus-within,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:hover{ scrollbar-color:#fffc #0000; } @media (hover:none){ .wp-block-gallery.has-nested-images figure.wp-block-image figcaption{ scrollbar-color:#fffc #0000; } } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption img{ display:inline; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption a{ color:inherit; } .wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border img{ box-sizing:border-box; } .wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border>a,.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border>div,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded>a,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded>div{ flex:1 1 auto; } .wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border figcaption,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded figcaption{ background:none; color:inherit; flex:initial; margin:0; padding:10px 10px 9px; position:relative; text-shadow:none; } .wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border:before,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded:before{ content:none; } .wp-block-gallery.has-nested-images figcaption{ flex-basis:100%; flex-grow:1; text-align:center; } .wp-block-gallery.has-nested-images:not(.is-cropped) figure.wp-block-image:not(#individual-image){ margin-bottom:auto; margin-top:0; } .wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image){ align-self:inherit; } .wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image)>a,.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image)>div:not(.components-drop-zone){ display:flex; } .wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image) a,.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image) img{ flex:1 0 0%; height:100%; object-fit:cover; width:100%; } .wp-block-gallery.has-nested-images.columns-1 figure.wp-block-image:not(#individual-image){ width:100%; } @media (min-width:600px){ .wp-block-gallery.has-nested-images.columns-3 figure.wp-block-image:not(#individual-image){ width:calc(33.33333% - var(--wp--style--unstable-gallery-gap, 16px)*.66667); } .wp-block-gallery.has-nested-images.columns-4 figure.wp-block-image:not(#individual-image){ width:calc(25% - var(--wp--style--unstable-gallery-gap, 16px)*.75); } .wp-block-gallery.has-nested-images.columns-5 figure.wp-block-image:not(#individual-image){ width:calc(20% - var(--wp--style--unstable-gallery-gap, 16px)*.8); } .wp-block-gallery.has-nested-images.columns-6 figure.wp-block-image:not(#individual-image){ width:calc(16.66667% - var(--wp--style--unstable-gallery-gap, 16px)*.83333); } .wp-block-gallery.has-nested-images.columns-7 figure.wp-block-image:not(#individual-image){ width:calc(14.28571% - var(--wp--style--unstable-gallery-gap, 16px)*.85714); } .wp-block-gallery.has-nested-images.columns-8 figure.wp-block-image:not(#individual-image){ width:calc(12.5% - var(--wp--style--unstable-gallery-gap, 16px)*.875); } .wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image){ width:calc(33.33% - var(--wp--style--unstable-gallery-gap, 16px)*.66667); } .wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2),.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2)~figure.wp-block-image:not(#individual-image){ width:calc(50% - var(--wp--style--unstable-gallery-gap, 16px)*.5); } .wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:last-child{ width:100%; } } .wp-block-gallery.has-nested-images.alignleft,.wp-block-gallery.has-nested-images.alignright{ max-width:420px; width:100%; } .wp-block-gallery.has-nested-images.aligncenter{ justify-content:center; }PKt\M40g gallery/editor-rtl.cssnu[:root :where(figure.wp-block-gallery){ display:block; } :root :where(figure.wp-block-gallery)>.blocks-gallery-caption{ flex:0 0 100%; } :root :where(figure.wp-block-gallery)>.blocks-gallery-media-placeholder-wrapper{ flex-basis:100%; } :root :where(figure.wp-block-gallery) .wp-block-image .components-notice.is-error{ display:block; } :root :where(figure.wp-block-gallery) .wp-block-image .components-notice__content{ margin:4px 0; } :root :where(figure.wp-block-gallery) .wp-block-image .components-notice__dismiss{ left:5px; position:absolute; top:0; } :root :where(figure.wp-block-gallery) .block-editor-media-placeholder.is-appender .components-placeholder__label{ display:none; } :root :where(figure.wp-block-gallery) .block-editor-media-placeholder.is-appender .block-editor-media-placeholder__button{ margin-bottom:0; } :root :where(figure.wp-block-gallery) .block-editor-media-placeholder{ margin:0; } :root :where(figure.wp-block-gallery) .block-editor-media-placeholder .components-placeholder__label{ display:flex; } :root :where(figure.wp-block-gallery) .block-editor-media-placeholder figcaption{ z-index:2; } :root :where(figure.wp-block-gallery) .components-spinner{ margin-right:-9px; margin-top:-9px; position:absolute; right:50%; top:50%; } .gallery-settings-buttons .components-button:first-child{ margin-left:8px; } .gallery-image-sizes .components-base-control__label{ margin-bottom:4px; } .gallery-image-sizes .gallery-image-sizes__loading{ align-items:center; color:#757575; display:flex; font-size:12px; } .gallery-image-sizes .components-spinner{ margin:0 4px 0 8px; } .blocks-gallery-item figure:not(.is-selected):focus,.blocks-gallery-item img:focus{ outline:none; } .blocks-gallery-item figure.is-selected:before{ bottom:0; box-shadow:0 0 0 1px #fff inset, 0 0 0 3px var(--wp-admin-theme-color) inset; content:""; left:0; outline:2px solid #0000; pointer-events:none; position:absolute; right:0; top:0; z-index:1; } .blocks-gallery-item figure.is-transient img{ opacity:.3; } .blocks-gallery-item .block-editor-media-placeholder{ height:100%; margin:0; } .blocks-gallery-item .block-editor-media-placeholder .components-placeholder__label{ display:flex; } .wp-block-gallery ul.blocks-gallery-grid{ margin:0; padding:0; } @media (min-width:600px){ .wp-block-update-gallery-modal{ max-width:480px; } } .wp-block-update-gallery-modal-buttons{ display:flex; gap:12px; justify-content:flex-end; }PKt\#digallery/theme-rtl.cssnu[.blocks-gallery-caption{ color:#555; font-size:13px; text-align:center; } .is-dark-theme .blocks-gallery-caption{ color:#ffffffa6; }PKt\g*8A8Agallery/style.cssnu[.blocks-gallery-grid:not(.has-nested-images),.wp-block-gallery:not(.has-nested-images){ display:flex; flex-wrap:wrap; list-style-type:none; margin:0; padding:0; } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item{ display:flex; flex-direction:column; flex-grow:1; justify-content:center; margin:0 1em 1em 0; position:relative; width:calc(50% - 1em); } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item:nth-of-type(2n){ margin-right:0; } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figure,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figure,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figure,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figure{ align-items:flex-end; display:flex; height:100%; justify-content:flex-start; margin:0; } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image img,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item img{ display:block; height:auto; max-width:100%; width:auto; } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figcaption,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figcaption,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figcaption,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figcaption{ background:linear-gradient(0deg, #000000b3, #0000004d 70%, #0000); bottom:0; box-sizing:border-box; color:#fff; font-size:.8em; margin:0; max-height:100%; overflow:auto; padding:3em .77em .7em; position:absolute; text-align:center; width:100%; z-index:2; } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figcaption img,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figcaption img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figcaption img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figcaption img{ display:inline; } .blocks-gallery-grid:not(.has-nested-images) figcaption,.wp-block-gallery:not(.has-nested-images) figcaption{ flex-grow:1; } .blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-image a,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-image img,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-item a,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-item img,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-image a,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-image img,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-item a,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-item img{ flex:1; height:100%; object-fit:cover; width:100%; } .blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-item{ margin-right:0; width:100%; } @media (min-width:600px){ .blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-item{ margin-right:1em; width:calc(33.33333% - .66667em); } .blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-item{ margin-right:1em; width:calc(25% - .75em); } .blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-item{ margin-right:1em; width:calc(20% - .8em); } .blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-item{ margin-right:1em; width:calc(16.66667% - .83333em); } .blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-item{ margin-right:1em; width:calc(14.28571% - .85714em); } .blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-item{ margin-right:1em; width:calc(12.5% - .875em); } .blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-image:nth-of-type(1n),.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-item:nth-of-type(1n),.blocks-gallery-grid:not(.has-nested-images).columns-2 .blocks-gallery-image:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images).columns-2 .blocks-gallery-item:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-image:nth-of-type(3n),.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-item:nth-of-type(3n),.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-image:nth-of-type(4n),.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-item:nth-of-type(4n),.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-image:nth-of-type(5n),.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-item:nth-of-type(5n),.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-image:nth-of-type(6n),.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-item:nth-of-type(6n),.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-image:nth-of-type(7n),.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-item:nth-of-type(7n),.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-image:nth-of-type(8n),.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-item:nth-of-type(8n),.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-image:nth-of-type(1n),.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-item:nth-of-type(1n),.wp-block-gallery:not(.has-nested-images).columns-2 .blocks-gallery-image:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images).columns-2 .blocks-gallery-item:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-image:nth-of-type(3n),.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-item:nth-of-type(3n),.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-image:nth-of-type(4n),.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-item:nth-of-type(4n),.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-image:nth-of-type(5n),.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-item:nth-of-type(5n),.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-image:nth-of-type(6n),.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-item:nth-of-type(6n),.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-image:nth-of-type(7n),.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-item:nth-of-type(7n),.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-image:nth-of-type(8n),.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-item:nth-of-type(8n){ margin-right:0; } } .blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image:last-child,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item:last-child,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image:last-child,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item:last-child{ margin-right:0; } .blocks-gallery-grid:not(.has-nested-images).alignleft,.blocks-gallery-grid:not(.has-nested-images).alignright,.wp-block-gallery:not(.has-nested-images).alignleft,.wp-block-gallery:not(.has-nested-images).alignright{ max-width:420px; width:100%; } .blocks-gallery-grid:not(.has-nested-images).aligncenter .blocks-gallery-item figure,.wp-block-gallery:not(.has-nested-images).aligncenter .blocks-gallery-item figure{ justify-content:center; } .wp-block-gallery:not(.is-cropped) .blocks-gallery-item{ align-self:flex-start; } figure.wp-block-gallery.has-nested-images{ align-items:normal; } .wp-block-gallery.has-nested-images figure.wp-block-image:not(#individual-image){ margin:0; width:calc(50% - var(--wp--style--unstable-gallery-gap, 16px)/2); } .wp-block-gallery.has-nested-images figure.wp-block-image{ box-sizing:border-box; display:flex; flex-direction:column; flex-grow:1; justify-content:center; max-width:100%; position:relative; } .wp-block-gallery.has-nested-images figure.wp-block-image>a,.wp-block-gallery.has-nested-images figure.wp-block-image>div{ flex-direction:column; flex-grow:1; margin:0; } .wp-block-gallery.has-nested-images figure.wp-block-image img{ display:block; height:auto; max-width:100% !important; width:auto; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption,.wp-block-gallery.has-nested-images figure.wp-block-image:has(figcaption):before{ bottom:0; left:0; max-height:100%; position:absolute; right:0; } .wp-block-gallery.has-nested-images figure.wp-block-image:has(figcaption):before{ -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px); content:""; height:100%; -webkit-mask-image:linear-gradient(0deg, #000 20%, #0000); mask-image:linear-gradient(0deg, #000 20%, #0000); max-height:40%; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption{ background:linear-gradient(0deg, #0006, #0000); box-sizing:border-box; color:#fff; font-size:13px; margin:0; overflow:auto; padding:1em; scrollbar-color:#0000 #0000; scrollbar-gutter:stable both-edges; scrollbar-width:thin; text-align:center; text-shadow:0 0 1.5px #000; will-change:transform; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar{ height:12px; width:12px; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar-track{ background-color:initial; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar-thumb{ background-clip:padding-box; background-color:initial; border:3px solid #0000; border-radius:8px; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus-within::-webkit-scrollbar-thumb,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus::-webkit-scrollbar-thumb,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:hover::-webkit-scrollbar-thumb{ background-color:#fffc; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus-within,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:hover{ scrollbar-color:#fffc #0000; } @media (hover:none){ .wp-block-gallery.has-nested-images figure.wp-block-image figcaption{ scrollbar-color:#fffc #0000; } } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption img{ display:inline; } .wp-block-gallery.has-nested-images figure.wp-block-image figcaption a{ color:inherit; } .wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border img{ box-sizing:border-box; } .wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border>a,.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border>div,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded>a,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded>div{ flex:1 1 auto; } .wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border figcaption,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded figcaption{ background:none; color:inherit; flex:initial; margin:0; padding:10px 10px 9px; position:relative; text-shadow:none; } .wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border:before,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded:before{ content:none; } .wp-block-gallery.has-nested-images figcaption{ flex-basis:100%; flex-grow:1; text-align:center; } .wp-block-gallery.has-nested-images:not(.is-cropped) figure.wp-block-image:not(#individual-image){ margin-bottom:auto; margin-top:0; } .wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image){ align-self:inherit; } .wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image)>a,.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image)>div:not(.components-drop-zone){ display:flex; } .wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image) a,.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image) img{ flex:1 0 0%; height:100%; object-fit:cover; width:100%; } .wp-block-gallery.has-nested-images.columns-1 figure.wp-block-image:not(#individual-image){ width:100%; } @media (min-width:600px){ .wp-block-gallery.has-nested-images.columns-3 figure.wp-block-image:not(#individual-image){ width:calc(33.33333% - var(--wp--style--unstable-gallery-gap, 16px)*.66667); } .wp-block-gallery.has-nested-images.columns-4 figure.wp-block-image:not(#individual-image){ width:calc(25% - var(--wp--style--unstable-gallery-gap, 16px)*.75); } .wp-block-gallery.has-nested-images.columns-5 figure.wp-block-image:not(#individual-image){ width:calc(20% - var(--wp--style--unstable-gallery-gap, 16px)*.8); } .wp-block-gallery.has-nested-images.columns-6 figure.wp-block-image:not(#individual-image){ width:calc(16.66667% - var(--wp--style--unstable-gallery-gap, 16px)*.83333); } .wp-block-gallery.has-nested-images.columns-7 figure.wp-block-image:not(#individual-image){ width:calc(14.28571% - var(--wp--style--unstable-gallery-gap, 16px)*.85714); } .wp-block-gallery.has-nested-images.columns-8 figure.wp-block-image:not(#individual-image){ width:calc(12.5% - var(--wp--style--unstable-gallery-gap, 16px)*.875); } .wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image){ width:calc(33.33% - var(--wp--style--unstable-gallery-gap, 16px)*.66667); } .wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2),.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2)~figure.wp-block-image:not(#individual-image){ width:calc(50% - var(--wp--style--unstable-gallery-gap, 16px)*.5); } .wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:last-child{ width:100%; } } .wp-block-gallery.has-nested-images.alignleft,.wp-block-gallery.has-nested-images.alignright{ max-width:420px; width:100%; } .wp-block-gallery.has-nested-images.aligncenter{ justify-content:center; }PKt\ 3{{gallery/theme-rtl.min.cssnu[.blocks-gallery-caption{color:#555;font-size:13px;text-align:center}.is-dark-theme .blocks-gallery-caption{color:#ffffffa6}PKt\Bvt->->gallery/style-rtl.min.cssnu[.blocks-gallery-grid:not(.has-nested-images),.wp-block-gallery:not(.has-nested-images){display:flex;flex-wrap:wrap;list-style-type:none;margin:0;padding:0}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item{display:flex;flex-direction:column;flex-grow:1;justify-content:center;margin:0 0 1em 1em;position:relative;width:calc(50% - 1em)}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item:nth-of-type(2n){margin-left:0}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figure,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figure,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figure,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figure{align-items:flex-end;display:flex;height:100%;justify-content:flex-start;margin:0}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image img,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item img{display:block;height:auto;max-width:100%;width:auto}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figcaption,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figcaption,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figcaption,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figcaption{background:linear-gradient(0deg,#000000b3,#0000004d 70%,#0000);bottom:0;box-sizing:border-box;color:#fff;font-size:.8em;margin:0;max-height:100%;overflow:auto;padding:3em .77em .7em;position:absolute;text-align:center;width:100%;z-index:2}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image figcaption img,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item figcaption img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image figcaption img,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item figcaption img{display:inline}.blocks-gallery-grid:not(.has-nested-images) figcaption,.wp-block-gallery:not(.has-nested-images) figcaption{flex-grow:1}.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-image a,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-image img,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-item a,.blocks-gallery-grid:not(.has-nested-images).is-cropped .blocks-gallery-item img,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-image a,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-image img,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-item a,.wp-block-gallery:not(.has-nested-images).is-cropped .blocks-gallery-item img{flex:1;height:100%;object-fit:cover;width:100%}.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-item{margin-left:0;width:100%}@media (min-width:600px){.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-item{margin-left:1em;width:calc(33.33333% - .66667em)}.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-item{margin-left:1em;width:calc(25% - .75em)}.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-item{margin-left:1em;width:calc(20% - .8em)}.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-item{margin-left:1em;width:calc(16.66667% - .83333em)}.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-item{margin-left:1em;width:calc(14.28571% - .85714em)}.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-image,.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-item,.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-image,.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-item{margin-left:1em;width:calc(12.5% - .875em)}.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-image:nth-of-type(1n),.blocks-gallery-grid:not(.has-nested-images).columns-1 .blocks-gallery-item:nth-of-type(1n),.blocks-gallery-grid:not(.has-nested-images).columns-2 .blocks-gallery-image:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images).columns-2 .blocks-gallery-item:nth-of-type(2n),.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-image:nth-of-type(3n),.blocks-gallery-grid:not(.has-nested-images).columns-3 .blocks-gallery-item:nth-of-type(3n),.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-image:nth-of-type(4n),.blocks-gallery-grid:not(.has-nested-images).columns-4 .blocks-gallery-item:nth-of-type(4n),.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-image:nth-of-type(5n),.blocks-gallery-grid:not(.has-nested-images).columns-5 .blocks-gallery-item:nth-of-type(5n),.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-image:nth-of-type(6n),.blocks-gallery-grid:not(.has-nested-images).columns-6 .blocks-gallery-item:nth-of-type(6n),.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-image:nth-of-type(7n),.blocks-gallery-grid:not(.has-nested-images).columns-7 .blocks-gallery-item:nth-of-type(7n),.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-image:nth-of-type(8n),.blocks-gallery-grid:not(.has-nested-images).columns-8 .blocks-gallery-item:nth-of-type(8n),.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-image:nth-of-type(1n),.wp-block-gallery:not(.has-nested-images).columns-1 .blocks-gallery-item:nth-of-type(1n),.wp-block-gallery:not(.has-nested-images).columns-2 .blocks-gallery-image:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images).columns-2 .blocks-gallery-item:nth-of-type(2n),.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-image:nth-of-type(3n),.wp-block-gallery:not(.has-nested-images).columns-3 .blocks-gallery-item:nth-of-type(3n),.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-image:nth-of-type(4n),.wp-block-gallery:not(.has-nested-images).columns-4 .blocks-gallery-item:nth-of-type(4n),.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-image:nth-of-type(5n),.wp-block-gallery:not(.has-nested-images).columns-5 .blocks-gallery-item:nth-of-type(5n),.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-image:nth-of-type(6n),.wp-block-gallery:not(.has-nested-images).columns-6 .blocks-gallery-item:nth-of-type(6n),.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-image:nth-of-type(7n),.wp-block-gallery:not(.has-nested-images).columns-7 .blocks-gallery-item:nth-of-type(7n),.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-image:nth-of-type(8n),.wp-block-gallery:not(.has-nested-images).columns-8 .blocks-gallery-item:nth-of-type(8n){margin-left:0}}.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-image:last-child,.blocks-gallery-grid:not(.has-nested-images) .blocks-gallery-item:last-child,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-image:last-child,.wp-block-gallery:not(.has-nested-images) .blocks-gallery-item:last-child{margin-left:0}.blocks-gallery-grid:not(.has-nested-images).alignleft,.blocks-gallery-grid:not(.has-nested-images).alignright,.wp-block-gallery:not(.has-nested-images).alignleft,.wp-block-gallery:not(.has-nested-images).alignright{max-width:420px;width:100%}.blocks-gallery-grid:not(.has-nested-images).aligncenter .blocks-gallery-item figure,.wp-block-gallery:not(.has-nested-images).aligncenter .blocks-gallery-item figure{justify-content:center}.wp-block-gallery:not(.is-cropped) .blocks-gallery-item{align-self:flex-start}figure.wp-block-gallery.has-nested-images{align-items:normal}.wp-block-gallery.has-nested-images figure.wp-block-image:not(#individual-image){margin:0;width:calc(50% - var(--wp--style--unstable-gallery-gap, 16px)/2)}.wp-block-gallery.has-nested-images figure.wp-block-image{box-sizing:border-box;display:flex;flex-direction:column;flex-grow:1;justify-content:center;max-width:100%;position:relative}.wp-block-gallery.has-nested-images figure.wp-block-image>a,.wp-block-gallery.has-nested-images figure.wp-block-image>div{flex-direction:column;flex-grow:1;margin:0}.wp-block-gallery.has-nested-images figure.wp-block-image img{display:block;height:auto;max-width:100%!important;width:auto}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption,.wp-block-gallery.has-nested-images figure.wp-block-image:has(figcaption):before{bottom:0;left:0;max-height:100%;position:absolute;right:0}.wp-block-gallery.has-nested-images figure.wp-block-image:has(figcaption):before{-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);content:"";height:100%;-webkit-mask-image:linear-gradient(0deg,#000 20%,#0000);mask-image:linear-gradient(0deg,#000 20%,#0000);max-height:40%}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption{background:linear-gradient(0deg,#0006,#0000);box-sizing:border-box;color:#fff;font-size:13px;margin:0;overflow:auto;padding:1em;scrollbar-color:#0000 #0000;scrollbar-gutter:stable both-edges;scrollbar-width:thin;text-align:center;text-shadow:0 0 1.5px #000;will-change:transform}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar{height:12px;width:12px}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar-track{background-color:initial}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption::-webkit-scrollbar-thumb{background-clip:padding-box;background-color:initial;border:3px solid #0000;border-radius:8px}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus-within::-webkit-scrollbar-thumb,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus::-webkit-scrollbar-thumb,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:hover::-webkit-scrollbar-thumb{background-color:#fffc}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:focus-within,.wp-block-gallery.has-nested-images figure.wp-block-image figcaption:hover{scrollbar-color:#fffc #0000}@media (hover:none){.wp-block-gallery.has-nested-images figure.wp-block-image figcaption{scrollbar-color:#fffc #0000}}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption img{display:inline}.wp-block-gallery.has-nested-images figure.wp-block-image figcaption a{color:inherit}.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border img{box-sizing:border-box}.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border>a,.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border>div,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded>a,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded>div{flex:1 1 auto}.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border figcaption,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded figcaption{background:none;color:inherit;flex:initial;margin:0;padding:10px 10px 9px;position:relative;text-shadow:none}.wp-block-gallery.has-nested-images figure.wp-block-image.has-custom-border:before,.wp-block-gallery.has-nested-images figure.wp-block-image.is-style-rounded:before{content:none}.wp-block-gallery.has-nested-images figcaption{flex-basis:100%;flex-grow:1;text-align:center}.wp-block-gallery.has-nested-images:not(.is-cropped) figure.wp-block-image:not(#individual-image){margin-bottom:auto;margin-top:0}.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image){align-self:inherit}.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image)>a,.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image)>div:not(.components-drop-zone){display:flex}.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image) a,.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image) img{flex:1 0 0%;height:100%;object-fit:cover;width:100%}.wp-block-gallery.has-nested-images.columns-1 figure.wp-block-image:not(#individual-image){width:100%}@media (min-width:600px){.wp-block-gallery.has-nested-images.columns-3 figure.wp-block-image:not(#individual-image){width:calc(33.33333% - var(--wp--style--unstable-gallery-gap, 16px)*.66667)}.wp-block-gallery.has-nested-images.columns-4 figure.wp-block-image:not(#individual-image){width:calc(25% - var(--wp--style--unstable-gallery-gap, 16px)*.75)}.wp-block-gallery.has-nested-images.columns-5 figure.wp-block-image:not(#individual-image){width:calc(20% - var(--wp--style--unstable-gallery-gap, 16px)*.8)}.wp-block-gallery.has-nested-images.columns-6 figure.wp-block-image:not(#individual-image){width:calc(16.66667% - var(--wp--style--unstable-gallery-gap, 16px)*.83333)}.wp-block-gallery.has-nested-images.columns-7 figure.wp-block-image:not(#individual-image){width:calc(14.28571% - var(--wp--style--unstable-gallery-gap, 16px)*.85714)}.wp-block-gallery.has-nested-images.columns-8 figure.wp-block-image:not(#individual-image){width:calc(12.5% - var(--wp--style--unstable-gallery-gap, 16px)*.875)}.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image){width:calc(33.33% - var(--wp--style--unstable-gallery-gap, 16px)*.66667)}.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2),.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2)~figure.wp-block-image:not(#individual-image){width:calc(50% - var(--wp--style--unstable-gallery-gap, 16px)*.5)}.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:last-child{width:100%}}.wp-block-gallery.has-nested-images.alignleft,.wp-block-gallery.has-nested-images.alignright{max-width:420px;width:100%}.wp-block-gallery.has-nested-images.aligncenter{justify-content:center}PKt\k]rgallery/editor-rtl.min.cssnu[:root :where(figure.wp-block-gallery){display:block}:root :where(figure.wp-block-gallery)>.blocks-gallery-caption{flex:0 0 100%}:root :where(figure.wp-block-gallery)>.blocks-gallery-media-placeholder-wrapper{flex-basis:100%}:root :where(figure.wp-block-gallery) .wp-block-image .components-notice.is-error{display:block}:root :where(figure.wp-block-gallery) .wp-block-image .components-notice__content{margin:4px 0}:root :where(figure.wp-block-gallery) .wp-block-image .components-notice__dismiss{left:5px;position:absolute;top:0}:root :where(figure.wp-block-gallery) .block-editor-media-placeholder.is-appender .components-placeholder__label{display:none}:root :where(figure.wp-block-gallery) .block-editor-media-placeholder.is-appender .block-editor-media-placeholder__button{margin-bottom:0}:root :where(figure.wp-block-gallery) .block-editor-media-placeholder{margin:0}:root :where(figure.wp-block-gallery) .block-editor-media-placeholder .components-placeholder__label{display:flex}:root :where(figure.wp-block-gallery) .block-editor-media-placeholder figcaption{z-index:2}:root :where(figure.wp-block-gallery) .components-spinner{margin-right:-9px;margin-top:-9px;position:absolute;right:50%;top:50%}.gallery-settings-buttons .components-button:first-child{margin-left:8px}.gallery-image-sizes .components-base-control__label{margin-bottom:4px}.gallery-image-sizes .gallery-image-sizes__loading{align-items:center;color:#757575;display:flex;font-size:12px}.gallery-image-sizes .components-spinner{margin:0 4px 0 8px}.blocks-gallery-item figure:not(.is-selected):focus,.blocks-gallery-item img:focus{outline:none}.blocks-gallery-item figure.is-selected:before{bottom:0;box-shadow:0 0 0 1px #fff inset,0 0 0 3px var(--wp-admin-theme-color) inset;content:"";left:0;outline:2px solid #0000;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.blocks-gallery-item figure.is-transient img{opacity:.3}.blocks-gallery-item .block-editor-media-placeholder{height:100%;margin:0}.blocks-gallery-item .block-editor-media-placeholder .components-placeholder__label{display:flex}.wp-block-gallery ul.blocks-gallery-grid{margin:0;padding:0}@media (min-width:600px){.wp-block-update-gallery-modal{max-width:480px}}.wp-block-update-gallery-modal-buttons{display:flex;gap:12px;justify-content:flex-end}PKt\}?$ gallery/editor.cssnu[:root :where(figure.wp-block-gallery){ display:block; } :root :where(figure.wp-block-gallery)>.blocks-gallery-caption{ flex:0 0 100%; } :root :where(figure.wp-block-gallery)>.blocks-gallery-media-placeholder-wrapper{ flex-basis:100%; } :root :where(figure.wp-block-gallery) .wp-block-image .components-notice.is-error{ display:block; } :root :where(figure.wp-block-gallery) .wp-block-image .components-notice__content{ margin:4px 0; } :root :where(figure.wp-block-gallery) .wp-block-image .components-notice__dismiss{ position:absolute; right:5px; top:0; } :root :where(figure.wp-block-gallery) .block-editor-media-placeholder.is-appender .components-placeholder__label{ display:none; } :root :where(figure.wp-block-gallery) .block-editor-media-placeholder.is-appender .block-editor-media-placeholder__button{ margin-bottom:0; } :root :where(figure.wp-block-gallery) .block-editor-media-placeholder{ margin:0; } :root :where(figure.wp-block-gallery) .block-editor-media-placeholder .components-placeholder__label{ display:flex; } :root :where(figure.wp-block-gallery) .block-editor-media-placeholder figcaption{ z-index:2; } :root :where(figure.wp-block-gallery) .components-spinner{ left:50%; margin-left:-9px; margin-top:-9px; position:absolute; top:50%; } .gallery-settings-buttons .components-button:first-child{ margin-right:8px; } .gallery-image-sizes .components-base-control__label{ margin-bottom:4px; } .gallery-image-sizes .gallery-image-sizes__loading{ align-items:center; color:#757575; display:flex; font-size:12px; } .gallery-image-sizes .components-spinner{ margin:0 8px 0 4px; } .blocks-gallery-item figure:not(.is-selected):focus,.blocks-gallery-item img:focus{ outline:none; } .blocks-gallery-item figure.is-selected:before{ bottom:0; box-shadow:0 0 0 1px #fff inset, 0 0 0 3px var(--wp-admin-theme-color) inset; content:""; left:0; outline:2px solid #0000; pointer-events:none; position:absolute; right:0; top:0; z-index:1; } .blocks-gallery-item figure.is-transient img{ opacity:.3; } .blocks-gallery-item .block-editor-media-placeholder{ height:100%; margin:0; } .blocks-gallery-item .block-editor-media-placeholder .components-placeholder__label{ display:flex; } .wp-block-gallery ul.blocks-gallery-grid{ margin:0; padding:0; } @media (min-width:600px){ .wp-block-update-gallery-modal{ max-width:480px; } } .wp-block-update-gallery-modal-buttons{ display:flex; gap:12px; justify-content:flex-end; }PKt\޻ calendar.phpnu[' . __( 'The calendar block is hidden because there are no published posts.' ) . '

            '; } return ''; } $previous_monthnum = $monthnum; $previous_year = $year; if ( isset( $attributes['month'] ) && isset( $attributes['year'] ) ) { $permalink_structure = get_option( 'permalink_structure' ); if ( str_contains( $permalink_structure, '%monthnum%' ) && str_contains( $permalink_structure, '%year%' ) ) { $monthnum = $attributes['month']; $year = $attributes['year']; } } $color_block_styles = array(); // Text color. $preset_text_color = array_key_exists( 'textColor', $attributes ) ? "var:preset|color|{$attributes['textColor']}" : null; $custom_text_color = $attributes['style']['color']['text'] ?? null; $color_block_styles['text'] = $preset_text_color ? $preset_text_color : $custom_text_color; // Background Color. $preset_background_color = array_key_exists( 'backgroundColor', $attributes ) ? "var:preset|color|{$attributes['backgroundColor']}" : null; $custom_background_color = $attributes['style']['color']['background'] ?? null; $color_block_styles['background'] = $preset_background_color ? $preset_background_color : $custom_background_color; // Generate color styles and classes. $styles = wp_style_engine_get_styles( array( 'color' => $color_block_styles ), array( 'convert_vars_to_classnames' => true ) ); $inline_styles = empty( $styles['css'] ) ? '' : sprintf( ' style="%s"', esc_attr( $styles['css'] ) ); $classnames = empty( $styles['classnames'] ) ? '' : ' ' . esc_attr( $styles['classnames'] ); if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { $classnames .= ' has-link-color'; } // Apply color classes and styles to the calendar. $calendar = str_replace( '%2$s', $wrapper_attributes, $calendar ); $monthnum = $previous_monthnum; $year = $previous_year; return $output; } /** * Registers the `core/calendar` block on server. * * @since 5.2.0 */ function register_block_core_calendar() { register_block_type_from_metadata( __DIR__ . '/calendar', array( 'render_callback' => 'render_block_core_calendar', ) ); } add_action( 'init', 'register_block_core_calendar' ); /** * Returns whether or not there are any published posts. * * Used to hide the calendar block when there are no published posts. * This compensates for a known Core bug: https://core.trac.wordpress.org/ticket/12016 * * @since 5.9.0 * * @return bool Has any published posts or not. */ function block_core_calendar_has_published_posts() { // Multisite already has an option that stores the count of the published posts. // Let's use that for multisites. if ( is_multisite() ) { return 0 < (int) get_option( 'post_count' ); } // On single sites we try our own cached option first. $has_published_posts = get_option( 'wp_calendar_block_has_published_posts', null ); if ( null !== $has_published_posts ) { return (bool) $has_published_posts; } // No cache hit, let's update the cache and return the cached value. return block_core_calendar_update_has_published_posts(); } /** * Queries the database for any published post and saves * a flag whether any published post exists or not. * * @since 5.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @return bool Has any published posts or not. */ function block_core_calendar_update_has_published_posts() { global $wpdb; $has_published_posts = (bool) $wpdb->get_var( "SELECT 1 as test FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish' LIMIT 1" ); update_option( 'wp_calendar_block_has_published_posts', $has_published_posts ); return $has_published_posts; } // We only want to register these functions and actions when // we are on single sites. On multi sites we use `post_count` option. if ( ! is_multisite() ) { /** * Handler for updating the has published posts flag when a post is deleted. * * @since 5.9.0 * * @param int $post_id Deleted post ID. */ function block_core_calendar_update_has_published_post_on_delete( $post_id ) { $post = get_post( $post_id ); if ( ! $post || 'publish' !== $post->post_status || 'post' !== $post->post_type ) { return; } block_core_calendar_update_has_published_posts(); } /** * Handler for updating the has published posts flag when a post status changes. * * @since 5.9.0 * * @param string $new_status The status the post is changing to. * @param string $old_status The status the post is changing from. * @param WP_Post $post Post object. */ function block_core_calendar_update_has_published_post_on_transition_post_status( $new_status, $old_status, $post ) { if ( $new_status === $old_status ) { return; } if ( 'post' !== get_post_type( $post ) ) { return; } if ( 'publish' !== $new_status && 'publish' !== $old_status ) { return; } block_core_calendar_update_has_published_posts(); } add_action( 'delete_post', 'block_core_calendar_update_has_published_post_on_delete' ); add_action( 'transition_post_status', 'block_core_calendar_update_has_published_post_on_transition_post_status', 10, 3 ); } PKt\$columns/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/columns", "title": "Columns", "category": "design", "allowedBlocks": [ "core/column" ], "description": "Display content in multiple columns, with blocks added to each column.", "textdomain": "default", "attributes": { "verticalAlignment": { "type": "string" }, "isStackedOnMobile": { "type": "boolean", "default": true }, "templateLock": { "type": [ "string", "boolean" ], "enum": [ "all", "insert", "contentOnly", false ] } }, "supports": { "anchor": true, "align": [ "wide", "full" ], "html": false, "color": { "gradients": true, "link": true, "heading": true, "button": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "spacing": { "blockGap": { "__experimentalDefault": "2em", "sides": [ "horizontal", "vertical" ] }, "margin": [ "top", "bottom" ], "padding": true, "__experimentalDefaultControls": { "padding": true, "blockGap": true } }, "layout": { "allowSwitching": false, "allowInheriting": false, "allowEditing": false, "default": { "type": "flex", "flexWrap": "nowrap" } }, "__experimentalBorder": { "color": true, "radius": true, "style": true, "width": true, "__experimentalDefaultControls": { "color": true, "radius": true, "style": true, "width": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "shadow": true }, "editorStyle": "wp-block-columns-editor", "style": "wp-block-columns" } PKt\+1+columns/editor.min.cssnu[.wp-block-columns :where(.wp-block){margin-left:0;margin-right:0;max-width:none}html :where(.wp-block-column){margin-bottom:0;margin-top:0}PKt\y columns/style.min.cssnu[.wp-block-columns{align-items:normal!important;box-sizing:border-box;display:flex;flex-wrap:wrap!important}@media (min-width:782px){.wp-block-columns{flex-wrap:nowrap!important}}.wp-block-columns.are-vertically-aligned-top{align-items:flex-start}.wp-block-columns.are-vertically-aligned-center{align-items:center}.wp-block-columns.are-vertically-aligned-bottom{align-items:flex-end}@media (max-width:781px){.wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column{flex-basis:100%!important}}@media (min-width:782px){.wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column{flex-basis:0;flex-grow:1}.wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column[style*=flex-basis]{flex-grow:0}}.wp-block-columns.is-not-stacked-on-mobile{flex-wrap:nowrap!important}.wp-block-columns.is-not-stacked-on-mobile>.wp-block-column{flex-basis:0;flex-grow:1}.wp-block-columns.is-not-stacked-on-mobile>.wp-block-column[style*=flex-basis]{flex-grow:0}:where(.wp-block-columns){margin-bottom:1.75em}:where(.wp-block-columns.has-background){padding:1.25em 2.375em}.wp-block-column{flex-grow:1;min-width:0;overflow-wrap:break-word;word-break:break-word}.wp-block-column.is-vertically-aligned-top{align-self:flex-start}.wp-block-column.is-vertically-aligned-center{align-self:center}.wp-block-column.is-vertically-aligned-bottom{align-self:flex-end}.wp-block-column.is-vertically-aligned-stretch{align-self:stretch}.wp-block-column.is-vertically-aligned-bottom,.wp-block-column.is-vertically-aligned-center,.wp-block-column.is-vertically-aligned-top{width:100%}PKt\.wp-block-column{ flex-basis:100% !important; } } @media (min-width:782px){ .wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column{ flex-basis:0; flex-grow:1; } .wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column[style*=flex-basis]{ flex-grow:0; } } .wp-block-columns.is-not-stacked-on-mobile{ flex-wrap:nowrap !important; } .wp-block-columns.is-not-stacked-on-mobile>.wp-block-column{ flex-basis:0; flex-grow:1; } .wp-block-columns.is-not-stacked-on-mobile>.wp-block-column[style*=flex-basis]{ flex-grow:0; } :where(.wp-block-columns){ margin-bottom:1.75em; } :where(.wp-block-columns.has-background){ padding:1.25em 2.375em; } .wp-block-column{ flex-grow:1; min-width:0; overflow-wrap:break-word; word-break:break-word; } .wp-block-column.is-vertically-aligned-top{ align-self:flex-start; } .wp-block-column.is-vertically-aligned-center{ align-self:center; } .wp-block-column.is-vertically-aligned-bottom{ align-self:flex-end; } .wp-block-column.is-vertically-aligned-stretch{ align-self:stretch; } .wp-block-column.is-vertically-aligned-bottom,.wp-block-column.is-vertically-aligned-center,.wp-block-column.is-vertically-aligned-top{ width:100%; }PKt\9.Ǡcolumns/editor-rtl.cssnu[.wp-block-columns :where(.wp-block){ margin-left:0; margin-right:0; max-width:none; } html :where(.wp-block-column){ margin-bottom:0; margin-top:0; }PKt\.wp-block-column{ flex-basis:100% !important; } } @media (min-width:782px){ .wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column{ flex-basis:0; flex-grow:1; } .wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column[style*=flex-basis]{ flex-grow:0; } } .wp-block-columns.is-not-stacked-on-mobile{ flex-wrap:nowrap !important; } .wp-block-columns.is-not-stacked-on-mobile>.wp-block-column{ flex-basis:0; flex-grow:1; } .wp-block-columns.is-not-stacked-on-mobile>.wp-block-column[style*=flex-basis]{ flex-grow:0; } :where(.wp-block-columns){ margin-bottom:1.75em; } :where(.wp-block-columns.has-background){ padding:1.25em 2.375em; } .wp-block-column{ flex-grow:1; min-width:0; overflow-wrap:break-word; word-break:break-word; } .wp-block-column.is-vertically-aligned-top{ align-self:flex-start; } .wp-block-column.is-vertically-aligned-center{ align-self:center; } .wp-block-column.is-vertically-aligned-bottom{ align-self:flex-end; } .wp-block-column.is-vertically-aligned-stretch{ align-self:stretch; } .wp-block-column.is-vertically-aligned-bottom,.wp-block-column.is-vertically-aligned-center,.wp-block-column.is-vertically-aligned-top{ width:100%; }PKt\y columns/style-rtl.min.cssnu[.wp-block-columns{align-items:normal!important;box-sizing:border-box;display:flex;flex-wrap:wrap!important}@media (min-width:782px){.wp-block-columns{flex-wrap:nowrap!important}}.wp-block-columns.are-vertically-aligned-top{align-items:flex-start}.wp-block-columns.are-vertically-aligned-center{align-items:center}.wp-block-columns.are-vertically-aligned-bottom{align-items:flex-end}@media (max-width:781px){.wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column{flex-basis:100%!important}}@media (min-width:782px){.wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column{flex-basis:0;flex-grow:1}.wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column[style*=flex-basis]{flex-grow:0}}.wp-block-columns.is-not-stacked-on-mobile{flex-wrap:nowrap!important}.wp-block-columns.is-not-stacked-on-mobile>.wp-block-column{flex-basis:0;flex-grow:1}.wp-block-columns.is-not-stacked-on-mobile>.wp-block-column[style*=flex-basis]{flex-grow:0}:where(.wp-block-columns){margin-bottom:1.75em}:where(.wp-block-columns.has-background){padding:1.25em 2.375em}.wp-block-column{flex-grow:1;min-width:0;overflow-wrap:break-word;word-break:break-word}.wp-block-column.is-vertically-aligned-top{align-self:flex-start}.wp-block-column.is-vertically-aligned-center{align-self:center}.wp-block-column.is-vertically-aligned-bottom{align-self:flex-end}.wp-block-column.is-vertically-aligned-stretch{align-self:stretch}.wp-block-column.is-vertically-aligned-bottom,.wp-block-column.is-vertically-aligned-center,.wp-block-column.is-vertically-aligned-top{width:100%}PKt\+1+columns/editor-rtl.min.cssnu[.wp-block-columns :where(.wp-block){margin-left:0;margin-right:0;max-width:none}html :where(.wp-block-column){margin-bottom:0;margin-top:0}PKt\9.Ǡcolumns/editor.cssnu[.wp-block-columns :where(.wp-block){ margin-left:0; margin-right:0; max-width:none; } html :where(.wp-block-column){ margin-bottom:0; margin-top:0; }PKt\query-pagination.phpnu[ __( 'Pagination' ), 'class' => $classes, ) ); return sprintf( '', $wrapper_attributes, $content ); } /** * Registers the `core/query-pagination` block on the server. * * @since 5.8.0 */ function register_block_core_query_pagination() { register_block_type_from_metadata( __DIR__ . '/query-pagination', array( 'render_callback' => 'render_block_core_query_pagination', ) ); } add_action( 'init', 'register_block_core_query_pagination' ); PKt\(C&&navigation-submenu.phpnu[ array(), 'inline_styles' => '', ); $has_named_font_size = array_key_exists( 'fontSize', $context ); $has_custom_font_size = isset( $context['style']['typography']['fontSize'] ); if ( $has_named_font_size ) { // Add the font size class. $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] ); } elseif ( $has_custom_font_size ) { // Add the custom font size inline style. $font_sizes['inline_styles'] = sprintf( 'font-size: %s;', wp_get_typography_font_size_value( array( 'size' => $context['style']['typography']['fontSize'], ) ) ); } return $font_sizes; } /** * Returns the top-level submenu SVG chevron icon. * * @since 5.9.0 * * @return string */ function block_core_navigation_submenu_render_submenu_icon() { return ''; } /** * Renders the `core/navigation-submenu` block. * * @since 5.9.0 * * @param array $attributes The block attributes. * @param string $content The saved content. * @param WP_Block $block The parsed block. * * @return string Returns the post content with the legacy widget added. */ function render_block_core_navigation_submenu( $attributes, $content, $block ) { $navigation_link_has_id = isset( $attributes['id'] ) && is_numeric( $attributes['id'] ); $is_post_type = isset( $attributes['kind'] ) && 'post-type' === $attributes['kind']; $is_post_type = $is_post_type || isset( $attributes['type'] ) && ( 'post' === $attributes['type'] || 'page' === $attributes['type'] ); // Don't render the block's subtree if it is a draft. if ( $is_post_type && $navigation_link_has_id && 'publish' !== get_post_status( $attributes['id'] ) ) { return ''; } // Don't render the block's subtree if it has no label. if ( empty( $attributes['label'] ) ) { return ''; } $font_sizes = block_core_navigation_submenu_build_css_font_sizes( $block->context ); $style_attribute = $font_sizes['inline_styles']; $has_submenu = count( $block->inner_blocks ) > 0; $kind = empty( $attributes['kind'] ) ? 'post_type' : str_replace( '-', '_', $attributes['kind'] ); $is_active = ! empty( $attributes['id'] ) && get_queried_object_id() === (int) $attributes['id'] && ! empty( get_queried_object()->$kind ); if ( is_post_type_archive() ) { $queried_archive_link = get_post_type_archive_link( get_queried_object()->name ); if ( $attributes['url'] === $queried_archive_link ) { $is_active = true; } } $show_submenu_indicators = isset( $block->context['showSubmenuIcon'] ) && $block->context['showSubmenuIcon']; $open_on_click = isset( $block->context['openSubmenusOnClick'] ) && $block->context['openSubmenusOnClick']; $open_on_hover_and_click = isset( $block->context['openSubmenusOnClick'] ) && ! $block->context['openSubmenusOnClick'] && $show_submenu_indicators; $classes = array( 'wp-block-navigation-item', ); $classes = array_merge( $classes, $font_sizes['css_classes'] ); if ( $has_submenu ) { $classes[] = 'has-child'; } if ( $open_on_click ) { $classes[] = 'open-on-click'; } if ( $open_on_hover_and_click ) { $classes[] = 'open-on-hover-click'; } if ( $is_active ) { $classes[] = 'current-menu-item'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ), 'style' => $style_attribute, ) ); $label = ''; if ( isset( $attributes['label'] ) ) { $label .= wp_kses_post( $attributes['label'] ); } $aria_label = sprintf( /* translators: Accessibility text. %s: Parent page title. */ __( '%s submenu' ), wp_strip_all_tags( $label ) ); $html = '
          • '; // If Submenus open on hover, we render an anchor tag with attributes. // If submenu icons are set to show, we also render a submenu button, so the submenu can be opened on click. if ( ! $open_on_click ) { $item_url = isset( $attributes['url'] ) ? $attributes['url'] : ''; // Start appending HTML attributes to anchor tag. $html .= '
          • %4s%5s', $wrapper_attributes, get_the_permalink( $post_ID ), esc_attr( $attributes['linkTarget'] ), $more_text, $screen_reader_text ); } /** * Registers the `core/read-more` block on the server. * * @since 6.0.0 */ function register_block_core_read_more() { register_block_type_from_metadata( __DIR__ . '/read-more', array( 'render_callback' => 'render_block_core_read_more', ) ); } add_action( 'init', 'register_block_core_read_more' ); PKt\7kYY search.phpnu[`. Support these by defaulting an undefined label and // buttonText to `__( 'Search' )`. $attributes = wp_parse_args( $attributes, array( 'label' => __( 'Search' ), 'buttonText' => __( 'Search' ), ) ); $input_id = wp_unique_id( 'wp-block-search__input-' ); $classnames = classnames_for_block_core_search( $attributes ); $show_label = ! empty( $attributes['showLabel'] ); $use_icon_button = ! empty( $attributes['buttonUseIcon'] ); $show_button = ( ! empty( $attributes['buttonPosition'] ) && 'no-button' === $attributes['buttonPosition'] ) ? false : true; $button_position = $show_button ? $attributes['buttonPosition'] : null; $query_params = ( ! empty( $attributes['query'] ) ) ? $attributes['query'] : array(); $button = ''; $query_params_markup = ''; $inline_styles = styles_for_block_core_search( $attributes ); $color_classes = get_color_classes_for_block_core_search( $attributes ); $typography_classes = get_typography_classes_for_block_core_search( $attributes ); $is_button_inside = ! empty( $attributes['buttonPosition'] ) && 'button-inside' === $attributes['buttonPosition']; // Border color classes need to be applied to the elements that have a border color. $border_color_classes = get_border_color_classes_for_block_core_search( $attributes ); // This variable is a constant and its value is always false at this moment. // It is defined this way because some values depend on it, in case it changes in the future. $open_by_default = false; $label_inner_html = empty( $attributes['label'] ) ? __( 'Search' ) : wp_kses_post( $attributes['label'] ); $label = new WP_HTML_Tag_Processor( sprintf( '', $inline_styles['label'], $label_inner_html ) ); if ( $label->next_tag() ) { $label->set_attribute( 'for', $input_id ); $label->add_class( 'wp-block-search__label' ); if ( $show_label && ! empty( $attributes['label'] ) ) { if ( ! empty( $typography_classes ) ) { $label->add_class( $typography_classes ); } } else { $label->add_class( 'screen-reader-text' ); } } $input = new WP_HTML_Tag_Processor( sprintf( '', $inline_styles['input'] ) ); $input_classes = array( 'wp-block-search__input' ); if ( ! $is_button_inside && ! empty( $border_color_classes ) ) { $input_classes[] = $border_color_classes; } if ( ! empty( $typography_classes ) ) { $input_classes[] = $typography_classes; } if ( $input->next_tag() ) { $input->add_class( implode( ' ', $input_classes ) ); $input->set_attribute( 'id', $input_id ); $input->set_attribute( 'value', get_search_query() ); $input->set_attribute( 'placeholder', $attributes['placeholder'] ); // If it's interactive, enqueue the script module and add the directives. $is_expandable_searchfield = 'button-only' === $button_position; if ( $is_expandable_searchfield ) { wp_enqueue_script_module( '@wordpress/block-library/search/view' ); $input->set_attribute( 'data-wp-bind--aria-hidden', '!context.isSearchInputVisible' ); $input->set_attribute( 'data-wp-bind--tabindex', 'state.tabindex' ); // Adding these attributes manually is needed until the Interactivity API // SSR logic is added to core. $input->set_attribute( 'aria-hidden', 'true' ); $input->set_attribute( 'tabindex', '-1' ); } } if ( count( $query_params ) > 0 ) { foreach ( $query_params as $param => $value ) { $query_params_markup .= sprintf( '', esc_attr( $param ), esc_attr( $value ) ); } } if ( $show_button ) { $button_classes = array( 'wp-block-search__button' ); $button_internal_markup = ''; if ( ! empty( $color_classes ) ) { $button_classes[] = $color_classes; } if ( ! empty( $typography_classes ) ) { $button_classes[] = $typography_classes; } if ( ! $is_button_inside && ! empty( $border_color_classes ) ) { $button_classes[] = $border_color_classes; } if ( ! $use_icon_button ) { if ( ! empty( $attributes['buttonText'] ) ) { $button_internal_markup = wp_kses_post( $attributes['buttonText'] ); } } else { $button_classes[] = 'has-icon'; $button_internal_markup = ' '; } // Include the button element class. $button_classes[] = wp_theme_get_element_class_name( 'button' ); $button = new WP_HTML_Tag_Processor( sprintf( '', $inline_styles['button'], $button_internal_markup ) ); if ( $button->next_tag() ) { $button->add_class( implode( ' ', $button_classes ) ); if ( 'button-only' === $attributes['buttonPosition'] ) { $button->set_attribute( 'data-wp-bind--aria-label', 'state.ariaLabel' ); $button->set_attribute( 'data-wp-bind--aria-controls', 'state.ariaControls' ); $button->set_attribute( 'data-wp-bind--aria-expanded', 'context.isSearchInputVisible' ); $button->set_attribute( 'data-wp-bind--type', 'state.type' ); $button->set_attribute( 'data-wp-on--click', 'actions.openSearchInput' ); // Adding these attributes manually is needed until the Interactivity // API SSR logic is added to core. $button->set_attribute( 'aria-label', __( 'Expand search field' ) ); $button->set_attribute( 'aria-controls', 'wp-block-search__input-' . $input_id ); $button->set_attribute( 'aria-expanded', 'false' ); $button->set_attribute( 'type', 'button' ); } else { $button->set_attribute( 'aria-label', wp_strip_all_tags( $attributes['buttonText'] ) ); } } } $field_markup_classes = $is_button_inside ? $border_color_classes : ''; $field_markup = sprintf( '
            %s
            ', esc_attr( $field_markup_classes ), $inline_styles['wrapper'], $input . $query_params_markup . $button ); $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classnames ) ); $form_directives = ''; // If it's interactive, add the directives. if ( $is_expandable_searchfield ) { $aria_label_expanded = __( 'Submit Search' ); $aria_label_collapsed = __( 'Expand search field' ); $form_context = wp_interactivity_data_wp_context( array( 'isSearchInputVisible' => $open_by_default, 'inputId' => $input_id, 'ariaLabelExpanded' => $aria_label_expanded, 'ariaLabelCollapsed' => $aria_label_collapsed, ) ); $form_directives = ' data-wp-interactive="core/search" ' . $form_context . ' data-wp-class--wp-block-search__searchfield-hidden="!context.isSearchInputVisible" data-wp-on-async--keydown="actions.handleSearchKeydown" data-wp-on-async--focusout="actions.handleSearchFocusout" '; } return sprintf( '
            %4s
            ', esc_url( home_url( '/' ) ), $wrapper_attributes, $form_directives, $label . $field_markup ); } /** * Registers the `core/search` block on the server. * * @since 5.2.0 */ function register_block_core_search() { register_block_type_from_metadata( __DIR__ . '/search', array( 'render_callback' => 'render_block_core_search', ) ); } add_action( 'init', 'register_block_core_search' ); /** * Builds the correct top level classnames for the 'core/search' block. * * @since 5.6.0 * * @param array $attributes The block attributes. * * @return string The classnames used in the block. */ function classnames_for_block_core_search( $attributes ) { $classnames = array(); if ( ! empty( $attributes['buttonPosition'] ) ) { if ( 'button-inside' === $attributes['buttonPosition'] ) { $classnames[] = 'wp-block-search__button-inside'; } if ( 'button-outside' === $attributes['buttonPosition'] ) { $classnames[] = 'wp-block-search__button-outside'; } if ( 'no-button' === $attributes['buttonPosition'] ) { $classnames[] = 'wp-block-search__no-button'; } if ( 'button-only' === $attributes['buttonPosition'] ) { $classnames[] = 'wp-block-search__button-only wp-block-search__searchfield-hidden'; } } if ( isset( $attributes['buttonUseIcon'] ) ) { if ( ! empty( $attributes['buttonPosition'] ) && 'no-button' !== $attributes['buttonPosition'] ) { if ( $attributes['buttonUseIcon'] ) { $classnames[] = 'wp-block-search__icon-button'; } else { $classnames[] = 'wp-block-search__text-button'; } } } return implode( ' ', $classnames ); } /** * This generates a CSS rule for the given border property and side if provided. * Based on whether the Search block is configured to display the button inside * or not, the generated rule is injected into the appropriate collection of * styles for later application in the block's markup. * * @since 6.1.0 * * @param array $attributes The block attributes. * @param string $property Border property to generate rule for e.g. width or color. * @param string $side Optional side border. The dictates the value retrieved and final CSS property. * @param array $wrapper_styles Current collection of wrapper styles. * @param array $button_styles Current collection of button styles. * @param array $input_styles Current collection of input styles. */ function apply_block_core_search_border_style( $attributes, $property, $side, &$wrapper_styles, &$button_styles, &$input_styles ) { $is_button_inside = isset( $attributes['buttonPosition'] ) && 'button-inside' === $attributes['buttonPosition']; $path = array( 'style', 'border', $property ); if ( $side ) { array_splice( $path, 2, 0, $side ); } $value = _wp_array_get( $attributes, $path, false ); if ( empty( $value ) ) { return; } if ( 'color' === $property && $side ) { $has_color_preset = str_contains( $value, 'var:preset|color|' ); if ( $has_color_preset ) { $named_color_value = substr( $value, strrpos( $value, '|' ) + 1 ); $value = sprintf( 'var(--wp--preset--color--%s)', $named_color_value ); } } $property_suffix = $side ? sprintf( '%s-%s', $side, $property ) : $property; if ( $is_button_inside ) { $wrapper_styles[] = sprintf( 'border-%s: %s;', $property_suffix, esc_attr( $value ) ); } else { $button_styles[] = sprintf( 'border-%s: %s;', $property_suffix, esc_attr( $value ) ); $input_styles[] = sprintf( 'border-%s: %s;', $property_suffix, esc_attr( $value ) ); } } /** * This adds CSS rules for a given border property e.g. width or color. It * injects rules into the provided wrapper, button and input style arrays for * uniform "flat" borders or those with individual sides configured. * * @since 6.1.0 * * @param array $attributes The block attributes. * @param string $property Border property to generate rule for e.g. width or color. * @param array $wrapper_styles Current collection of wrapper styles. * @param array $button_styles Current collection of button styles. * @param array $input_styles Current collection of input styles. */ function apply_block_core_search_border_styles( $attributes, $property, &$wrapper_styles, &$button_styles, &$input_styles ) { apply_block_core_search_border_style( $attributes, $property, null, $wrapper_styles, $button_styles, $input_styles ); apply_block_core_search_border_style( $attributes, $property, 'top', $wrapper_styles, $button_styles, $input_styles ); apply_block_core_search_border_style( $attributes, $property, 'right', $wrapper_styles, $button_styles, $input_styles ); apply_block_core_search_border_style( $attributes, $property, 'bottom', $wrapper_styles, $button_styles, $input_styles ); apply_block_core_search_border_style( $attributes, $property, 'left', $wrapper_styles, $button_styles, $input_styles ); } /** * Builds an array of inline styles for the search block. * * The result will contain one entry for shared styles such as those for the * inner input or button and a second for the inner wrapper should the block * be positioning the button "inside". * * @since 5.8.0 * * @param array $attributes The block attributes. * * @return array Style HTML attribute. */ function styles_for_block_core_search( $attributes ) { $wrapper_styles = array(); $button_styles = array(); $input_styles = array(); $label_styles = array(); $is_button_inside = ! empty( $attributes['buttonPosition'] ) && 'button-inside' === $attributes['buttonPosition']; $show_label = ( isset( $attributes['showLabel'] ) ) && false !== $attributes['showLabel']; // Add width styles. $has_width = ! empty( $attributes['width'] ) && ! empty( $attributes['widthUnit'] ); if ( $has_width ) { $wrapper_styles[] = sprintf( 'width: %d%s;', esc_attr( $attributes['width'] ), esc_attr( $attributes['widthUnit'] ) ); } // Add border width and color styles. apply_block_core_search_border_styles( $attributes, 'width', $wrapper_styles, $button_styles, $input_styles ); apply_block_core_search_border_styles( $attributes, 'color', $wrapper_styles, $button_styles, $input_styles ); apply_block_core_search_border_styles( $attributes, 'style', $wrapper_styles, $button_styles, $input_styles ); // Add border radius styles. $has_border_radius = ! empty( $attributes['style']['border']['radius'] ); if ( $has_border_radius ) { $default_padding = '4px'; $border_radius = $attributes['style']['border']['radius']; if ( is_array( $border_radius ) ) { // Apply styles for individual corner border radii. foreach ( $border_radius as $key => $value ) { if ( null !== $value ) { // Convert camelCase key to kebab-case. $name = strtolower( preg_replace( '/(? ! empty( $input_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $input_styles ) ) ) ) : '', 'button' => ! empty( $button_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $button_styles ) ) ) ) : '', 'wrapper' => ! empty( $wrapper_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $wrapper_styles ) ) ) ) : '', 'label' => ! empty( $label_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $label_styles ) ) ) ) : '', ); } /** * Returns typography classnames depending on whether there are named font sizes/families. * * @since 6.1.0 * * @param array $attributes The block attributes. * * @return string The typography color classnames to be applied to the block elements. */ function get_typography_classes_for_block_core_search( $attributes ) { $typography_classes = array(); $has_named_font_family = ! empty( $attributes['fontFamily'] ); $has_named_font_size = ! empty( $attributes['fontSize'] ); if ( $has_named_font_size ) { $typography_classes[] = sprintf( 'has-%s-font-size', esc_attr( $attributes['fontSize'] ) ); } if ( $has_named_font_family ) { $typography_classes[] = sprintf( 'has-%s-font-family', esc_attr( $attributes['fontFamily'] ) ); } return implode( ' ', $typography_classes ); } /** * Returns typography styles to be included in an HTML style tag. * This excludes text-decoration, which is applied only to the label and button elements of the search block. * * @since 6.1.0 * * @param array $attributes The block attributes. * * @return string A string of typography CSS declarations. */ function get_typography_styles_for_block_core_search( $attributes ) { $typography_styles = array(); // Add typography styles. if ( ! empty( $attributes['style']['typography']['fontSize'] ) ) { $typography_styles[] = sprintf( 'font-size: %s;', wp_get_typography_font_size_value( array( 'size' => $attributes['style']['typography']['fontSize'], ) ) ); } if ( ! empty( $attributes['style']['typography']['fontFamily'] ) ) { $typography_styles[] = sprintf( 'font-family: %s;', $attributes['style']['typography']['fontFamily'] ); } if ( ! empty( $attributes['style']['typography']['letterSpacing'] ) ) { $typography_styles[] = sprintf( 'letter-spacing: %s;', $attributes['style']['typography']['letterSpacing'] ); } if ( ! empty( $attributes['style']['typography']['fontWeight'] ) ) { $typography_styles[] = sprintf( 'font-weight: %s;', $attributes['style']['typography']['fontWeight'] ); } if ( ! empty( $attributes['style']['typography']['fontStyle'] ) ) { $typography_styles[] = sprintf( 'font-style: %s;', $attributes['style']['typography']['fontStyle'] ); } if ( ! empty( $attributes['style']['typography']['lineHeight'] ) ) { $typography_styles[] = sprintf( 'line-height: %s;', $attributes['style']['typography']['lineHeight'] ); } if ( ! empty( $attributes['style']['typography']['textTransform'] ) ) { $typography_styles[] = sprintf( 'text-transform: %s;', $attributes['style']['typography']['textTransform'] ); } return implode( '', $typography_styles ); } /** * Returns border color classnames depending on whether there are named or custom border colors. * * @since 5.9.0 * * @param array $attributes The block attributes. * * @return string The border color classnames to be applied to the block elements. */ function get_border_color_classes_for_block_core_search( $attributes ) { $border_color_classes = array(); $has_custom_border_color = ! empty( $attributes['style']['border']['color'] ); $has_named_border_color = ! empty( $attributes['borderColor'] ); if ( $has_custom_border_color || $has_named_border_color ) { $border_color_classes[] = 'has-border-color'; } if ( $has_named_border_color ) { $border_color_classes[] = sprintf( 'has-%s-border-color', esc_attr( $attributes['borderColor'] ) ); } return implode( ' ', $border_color_classes ); } /** * Returns color classnames depending on whether there are named or custom text and background colors. * * @since 5.9.0 * * @param array $attributes The block attributes. * * @return string The color classnames to be applied to the block elements. */ function get_color_classes_for_block_core_search( $attributes ) { $classnames = array(); // Text color. $has_named_text_color = ! empty( $attributes['textColor'] ); $has_custom_text_color = ! empty( $attributes['style']['color']['text'] ); if ( $has_named_text_color ) { $classnames[] = sprintf( 'has-text-color has-%s-color', $attributes['textColor'] ); } elseif ( $has_custom_text_color ) { // If a custom 'textColor' was selected instead of a preset, still add the generic `has-text-color` class. $classnames[] = 'has-text-color'; } // Background color. $has_named_background_color = ! empty( $attributes['backgroundColor'] ); $has_custom_background_color = ! empty( $attributes['style']['color']['background'] ); $has_named_gradient = ! empty( $attributes['gradient'] ); $has_custom_gradient = ! empty( $attributes['style']['color']['gradient'] ); if ( $has_named_background_color || $has_custom_background_color || $has_named_gradient || $has_custom_gradient ) { $classnames[] = 'has-background'; } if ( $has_named_background_color ) { $classnames[] = sprintf( 'has-%s-background-color', $attributes['backgroundColor'] ); } if ( $has_named_gradient ) { $classnames[] = sprintf( 'has-%s-gradient-background', $attributes['gradient'] ); } return implode( ' ', $classnames ); } PKt\shortcode/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/shortcode", "title": "Shortcode", "category": "widgets", "description": "Insert additional custom elements with a WordPress shortcode.", "textdomain": "default", "attributes": { "text": { "type": "string", "source": "raw" } }, "supports": { "className": false, "customClassName": false, "html": false }, "editorStyle": "wp-block-shortcode-editor" } PKt\ EAAshortcode/editor.min.cssnu[.blocks-shortcode__textarea{background:#fff!important;border:1px solid #1e1e1e!important;border-radius:2px!important;box-shadow:none!important;box-sizing:border-box;color:#1e1e1e!important;font-family:Menlo,Consolas,monaco,monospace!important;font-size:16px!important;max-height:250px;padding:12px!important;resize:none}@media (min-width:600px){.blocks-shortcode__textarea{font-size:13px!important}}.blocks-shortcode__textarea:focus{border-color:var(--wp-admin-theme-color)!important;box-shadow:0 0 0 1px var(--wp-admin-theme-color)!important;outline:2px solid #0000!important}PKt\Zdshortcode/editor-rtl.cssnu[.blocks-shortcode__textarea{ background:#fff !important; border:1px solid #1e1e1e !important; border-radius:2px !important; box-shadow:none !important; box-sizing:border-box; color:#1e1e1e !important; font-family:Menlo,Consolas,monaco,monospace !important; font-size:16px !important; max-height:250px; padding:12px !important; resize:none; } @media (min-width:600px){ .blocks-shortcode__textarea{ font-size:13px !important; } } .blocks-shortcode__textarea:focus{ border-color:var(--wp-admin-theme-color) !important; box-shadow:0 0 0 1px var(--wp-admin-theme-color) !important; outline:2px solid #0000 !important; }PKt\ EAAshortcode/editor-rtl.min.cssnu[.blocks-shortcode__textarea{background:#fff!important;border:1px solid #1e1e1e!important;border-radius:2px!important;box-shadow:none!important;box-sizing:border-box;color:#1e1e1e!important;font-family:Menlo,Consolas,monaco,monospace!important;font-size:16px!important;max-height:250px;padding:12px!important;resize:none}@media (min-width:600px){.blocks-shortcode__textarea{font-size:13px!important}}.blocks-shortcode__textarea:focus{border-color:var(--wp-admin-theme-color)!important;box-shadow:0 0 0 1px var(--wp-admin-theme-color)!important;outline:2px solid #0000!important}PKt\Zdshortcode/editor.cssnu[.blocks-shortcode__textarea{ background:#fff !important; border:1px solid #1e1e1e !important; border-radius:2px !important; box-shadow:none !important; box-sizing:border-box; color:#1e1e1e !important; font-family:Menlo,Consolas,monaco,monospace !important; font-size:16px !important; max-height:250px; padding:12px !important; resize:none; } @media (min-width:600px){ .blocks-shortcode__textarea{ font-size:13px !important; } } .blocks-shortcode__textarea:focus{ border-color:var(--wp-admin-theme-color) !important; box-shadow:0 0 0 1px var(--wp-admin-theme-color) !important; outline:2px solid #0000 !important; }PKt\YA``comments-pagination-numbers.phpnu[context['postId'] ) ) { return ''; } $comment_vars = build_comment_query_vars_from_block( $block ); $total = ( new WP_Comment_Query( $comment_vars ) )->max_num_pages; $current = ! empty( $comment_vars['paged'] ) ? $comment_vars['paged'] : null; // Render links. $content = paginate_comments_links( array( 'total' => $total, 'current' => $current, 'prev_next' => false, 'echo' => false, ) ); if ( empty( $content ) ) { return ''; } $wrapper_attributes = get_block_wrapper_attributes(); return sprintf( '
            %2$s
            ', $wrapper_attributes, $content ); } /** * Registers the `core/comments-pagination-numbers` block on the server. * * @since 6.0.0 */ function register_block_core_comments_pagination_numbers() { register_block_type_from_metadata( __DIR__ . '/comments-pagination-numbers', array( 'render_callback' => 'render_block_core_comments_pagination_numbers', ) ); } add_action( 'init', 'register_block_core_comments_pagination_numbers' ); PKt\|33group/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/group", "title": "Group", "category": "design", "description": "Gather blocks in a layout container.", "keywords": [ "container", "wrapper", "row", "section" ], "textdomain": "default", "attributes": { "tagName": { "type": "string", "default": "div" }, "templateLock": { "type": [ "string", "boolean" ], "enum": [ "all", "insert", "contentOnly", false ] }, "allowedBlocks": { "type": "array" } }, "supports": { "__experimentalOnEnter": true, "__experimentalOnMerge": true, "__experimentalSettings": true, "align": [ "wide", "full" ], "anchor": true, "ariaLabel": true, "html": false, "background": { "backgroundImage": true, "backgroundSize": true, "__experimentalDefaultControls": { "backgroundImage": true } }, "color": { "gradients": true, "heading": true, "button": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "shadow": true, "spacing": { "margin": [ "top", "bottom" ], "padding": true, "blockGap": true, "__experimentalDefaultControls": { "padding": true, "blockGap": true } }, "dimensions": { "minHeight": true }, "__experimentalBorder": { "color": true, "radius": true, "style": true, "width": true, "__experimentalDefaultControls": { "color": true, "radius": true, "style": true, "width": true } }, "position": { "sticky": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "layout": { "allowSizingOnChildren": true }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-group-editor", "style": "wp-block-group" } PKt\ףgroup/editor.min.cssnu[.wp-block-group .block-editor-block-list__insertion-point{left:0;right:0}[data-type="core/group"].is-selected .block-list-appender{margin-left:0;margin-right:0}[data-type="core/group"].is-selected .has-background .block-list-appender{margin-bottom:18px;margin-top:18px}.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child{gap:inherit;pointer-events:none}.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-default-block-appender__content,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-inserter{display:inherit;flex:1;flex-direction:inherit;width:100%}.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child:after{border:1px dashed;content:"";display:flex;flex:1 0 40px;min-height:38px;pointer-events:none}.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-button-block-appender,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-inserter{pointer-events:all}PKt\\DT>>group/theme.min.cssnu[:where(.wp-block-group.has-background){padding:1.25em 2.375em}PKt\z|'CCgroup/theme.cssnu[:where(.wp-block-group.has-background){ padding:1.25em 2.375em; }PKt\uugroup/style.min.cssnu[.wp-block-group{box-sizing:border-box}:where(.wp-block-group.wp-block-group-is-layout-constrained){position:relative}PKt\.tҁgroup/style-rtl.cssnu[.wp-block-group{ box-sizing:border-box; } :where(.wp-block-group.wp-block-group-is-layout-constrained){ position:relative; }PKt\?|<<group/editor-rtl.cssnu[.wp-block-group .block-editor-block-list__insertion-point{ left:0; right:0; } [data-type="core/group"].is-selected .block-list-appender{ margin-left:0; margin-right:0; } [data-type="core/group"].is-selected .has-background .block-list-appender{ margin-bottom:18px; margin-top:18px; } .wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child{ gap:inherit; pointer-events:none; } .wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-default-block-appender__content,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-inserter{ display:inherit; flex:1; flex-direction:inherit; width:100%; } .wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child:after{ border:1px dashed; content:""; display:flex; flex:1 0 40px; min-height:38px; pointer-events:none; } .wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-button-block-appender,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-inserter{ pointer-events:all; }PKt\z|'CCgroup/theme-rtl.cssnu[:where(.wp-block-group.has-background){ padding:1.25em 2.375em; }PKt\.tҁgroup/style.cssnu[.wp-block-group{ box-sizing:border-box; } :where(.wp-block-group.wp-block-group-is-layout-constrained){ position:relative; }PKt\\DT>>group/theme-rtl.min.cssnu[:where(.wp-block-group.has-background){padding:1.25em 2.375em}PKt\uugroup/style-rtl.min.cssnu[.wp-block-group{box-sizing:border-box}:where(.wp-block-group.wp-block-group-is-layout-constrained){position:relative}PKt\ףgroup/editor-rtl.min.cssnu[.wp-block-group .block-editor-block-list__insertion-point{left:0;right:0}[data-type="core/group"].is-selected .block-list-appender{margin-left:0;margin-right:0}[data-type="core/group"].is-selected .has-background .block-list-appender{margin-bottom:18px;margin-top:18px}.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child{gap:inherit;pointer-events:none}.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-default-block-appender__content,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-inserter{display:inherit;flex:1;flex-direction:inherit;width:100%}.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child:after{border:1px dashed;content:"";display:flex;flex:1 0 40px;min-height:38px;pointer-events:none}.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-button-block-appender,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-inserter{pointer-events:all}PKt\?|<<group/editor.cssnu[.wp-block-group .block-editor-block-list__insertion-point{ left:0; right:0; } [data-type="core/group"].is-selected .block-list-appender{ margin-left:0; margin-right:0; } [data-type="core/group"].is-selected .has-background .block-list-appender{ margin-bottom:18px; margin-top:18px; } .wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child{ gap:inherit; pointer-events:none; } .wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-default-block-appender__content,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-inserter{ display:inherit; flex:1; flex-direction:inherit; width:100%; } .wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child:after{ border:1px dashed; content:""; display:flex; flex:1 0 40px; min-height:38px; pointer-events:none; } .wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-button-block-appender,.wp-block-group.is-layout-flex.block-editor-block-list__block>.block-list-appender:only-child .block-editor-inserter{ pointer-events:all; }PKt\b%pullquote/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/pullquote", "title": "Pullquote", "category": "text", "description": "Give special visual emphasis to a quote from your text.", "textdomain": "default", "attributes": { "value": { "type": "rich-text", "source": "rich-text", "selector": "p", "role": "content" }, "citation": { "type": "rich-text", "source": "rich-text", "selector": "cite", "role": "content" }, "textAlign": { "type": "string" } }, "supports": { "anchor": true, "align": [ "left", "right", "wide", "full" ], "background": { "backgroundImage": true, "backgroundSize": true, "__experimentalDefaultControls": { "backgroundImage": true } }, "color": { "gradients": true, "background": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "dimensions": { "minHeight": true, "__experimentalDefaultControls": { "minHeight": false } }, "spacing": { "margin": true, "padding": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "__experimentalBorder": { "color": true, "radius": true, "style": true, "width": true, "__experimentalDefaultControls": { "color": true, "radius": true, "style": true, "width": true } }, "__experimentalStyle": { "typography": { "fontSize": "1.5em", "lineHeight": "1.6" } }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-pullquote-editor", "style": "wp-block-pullquote" } PKt\,japullquote/editor.min.cssnu[.wp-block-pullquote.is-style-solid-color blockquote p{font-size:32px}.wp-block-pullquote.is-style-solid-color .wp-block-pullquote__citation{font-style:normal;text-transform:none}.wp-block-pullquote .wp-block-pullquote__citation{color:inherit}PKt\U  pullquote/theme.min.cssnu[.wp-block-pullquote{border-bottom:4px solid;border-top:4px solid;color:currentColor;margin-bottom:1.75em}.wp-block-pullquote cite,.wp-block-pullquote footer,.wp-block-pullquote__citation{color:currentColor;font-size:.8125em;font-style:normal;text-transform:uppercase}PKt\R((pullquote/theme.cssnu[.wp-block-pullquote{ border-bottom:4px solid; border-top:4px solid; color:currentColor; margin-bottom:1.75em; } .wp-block-pullquote cite,.wp-block-pullquote footer,.wp-block-pullquote__citation{ color:currentColor; font-size:.8125em; font-style:normal; text-transform:uppercase; }PKt\J)oopullquote/style.min.cssnu[.wp-block-pullquote{box-sizing:border-box;margin:0 0 1em;overflow-wrap:break-word;padding:4em 0;text-align:center}.wp-block-pullquote blockquote,.wp-block-pullquote cite,.wp-block-pullquote p{color:inherit}.wp-block-pullquote blockquote{margin:0}.wp-block-pullquote p{margin-top:0}.wp-block-pullquote p:last-child{margin-bottom:0}.wp-block-pullquote.alignleft,.wp-block-pullquote.alignright{max-width:420px}.wp-block-pullquote cite,.wp-block-pullquote footer{position:relative}.wp-block-pullquote .has-text-color a{color:inherit}.wp-block-pullquote.has-text-align-left blockquote{text-align:left}.wp-block-pullquote.has-text-align-right blockquote{text-align:right}.wp-block-pullquote.has-text-align-center blockquote{text-align:center}.wp-block-pullquote.is-style-solid-color{border:none}.wp-block-pullquote.is-style-solid-color blockquote{margin-left:auto;margin-right:auto;max-width:60%}.wp-block-pullquote.is-style-solid-color blockquote p{font-size:2em;margin-bottom:0;margin-top:0}.wp-block-pullquote.is-style-solid-color blockquote cite{font-style:normal;text-transform:none}.wp-block-pullquote cite{color:inherit;display:block}PKt\ -pullquote/style-rtl.cssnu[.wp-block-pullquote{ box-sizing:border-box; margin:0 0 1em; overflow-wrap:break-word; padding:4em 0; text-align:center; } .wp-block-pullquote blockquote,.wp-block-pullquote cite,.wp-block-pullquote p{ color:inherit; } .wp-block-pullquote blockquote{ margin:0; } .wp-block-pullquote p{ margin-top:0; } .wp-block-pullquote p:last-child{ margin-bottom:0; } .wp-block-pullquote.alignleft,.wp-block-pullquote.alignright{ max-width:420px; } .wp-block-pullquote cite,.wp-block-pullquote footer{ position:relative; } .wp-block-pullquote .has-text-color a{ color:inherit; } .wp-block-pullquote.has-text-align-left blockquote{ text-align:right; } .wp-block-pullquote.has-text-align-right blockquote{ text-align:left; } .wp-block-pullquote.has-text-align-center blockquote{ text-align:center; } .wp-block-pullquote.is-style-solid-color{ border:none; } .wp-block-pullquote.is-style-solid-color blockquote{ margin-left:auto; margin-right:auto; max-width:60%; } .wp-block-pullquote.is-style-solid-color blockquote p{ font-size:2em; margin-bottom:0; margin-top:0; } .wp-block-pullquote.is-style-solid-color blockquote cite{ font-style:normal; text-transform:none; } .wp-block-pullquote cite{ color:inherit; display:block; }PKt\ijRpullquote/editor-rtl.cssnu[.wp-block-pullquote.is-style-solid-color blockquote p{ font-size:32px; } .wp-block-pullquote.is-style-solid-color .wp-block-pullquote__citation{ font-style:normal; text-transform:none; } .wp-block-pullquote .wp-block-pullquote__citation{ color:inherit; }PKt\R((pullquote/theme-rtl.cssnu[.wp-block-pullquote{ border-bottom:4px solid; border-top:4px solid; color:currentColor; margin-bottom:1.75em; } .wp-block-pullquote cite,.wp-block-pullquote footer,.wp-block-pullquote__citation{ color:currentColor; font-size:.8125em; font-style:normal; text-transform:uppercase; }PKt\ pullquote/style.cssnu[.wp-block-pullquote{ box-sizing:border-box; margin:0 0 1em; overflow-wrap:break-word; padding:4em 0; text-align:center; } .wp-block-pullquote blockquote,.wp-block-pullquote cite,.wp-block-pullquote p{ color:inherit; } .wp-block-pullquote blockquote{ margin:0; } .wp-block-pullquote p{ margin-top:0; } .wp-block-pullquote p:last-child{ margin-bottom:0; } .wp-block-pullquote.alignleft,.wp-block-pullquote.alignright{ max-width:420px; } .wp-block-pullquote cite,.wp-block-pullquote footer{ position:relative; } .wp-block-pullquote .has-text-color a{ color:inherit; } .wp-block-pullquote.has-text-align-left blockquote{ text-align:left; } .wp-block-pullquote.has-text-align-right blockquote{ text-align:right; } .wp-block-pullquote.has-text-align-center blockquote{ text-align:center; } .wp-block-pullquote.is-style-solid-color{ border:none; } .wp-block-pullquote.is-style-solid-color blockquote{ margin-left:auto; margin-right:auto; max-width:60%; } .wp-block-pullquote.is-style-solid-color blockquote p{ font-size:2em; margin-bottom:0; margin-top:0; } .wp-block-pullquote.is-style-solid-color blockquote cite{ font-style:normal; text-transform:none; } .wp-block-pullquote cite{ color:inherit; display:block; }PKt\U  pullquote/theme-rtl.min.cssnu[.wp-block-pullquote{border-bottom:4px solid;border-top:4px solid;color:currentColor;margin-bottom:1.75em}.wp-block-pullquote cite,.wp-block-pullquote footer,.wp-block-pullquote__citation{color:currentColor;font-size:.8125em;font-style:normal;text-transform:uppercase}PKt\koopullquote/style-rtl.min.cssnu[.wp-block-pullquote{box-sizing:border-box;margin:0 0 1em;overflow-wrap:break-word;padding:4em 0;text-align:center}.wp-block-pullquote blockquote,.wp-block-pullquote cite,.wp-block-pullquote p{color:inherit}.wp-block-pullquote blockquote{margin:0}.wp-block-pullquote p{margin-top:0}.wp-block-pullquote p:last-child{margin-bottom:0}.wp-block-pullquote.alignleft,.wp-block-pullquote.alignright{max-width:420px}.wp-block-pullquote cite,.wp-block-pullquote footer{position:relative}.wp-block-pullquote .has-text-color a{color:inherit}.wp-block-pullquote.has-text-align-left blockquote{text-align:right}.wp-block-pullquote.has-text-align-right blockquote{text-align:left}.wp-block-pullquote.has-text-align-center blockquote{text-align:center}.wp-block-pullquote.is-style-solid-color{border:none}.wp-block-pullquote.is-style-solid-color blockquote{margin-left:auto;margin-right:auto;max-width:60%}.wp-block-pullquote.is-style-solid-color blockquote p{font-size:2em;margin-bottom:0;margin-top:0}.wp-block-pullquote.is-style-solid-color blockquote cite{font-style:normal;text-transform:none}.wp-block-pullquote cite{color:inherit;display:block}PKt\,japullquote/editor-rtl.min.cssnu[.wp-block-pullquote.is-style-solid-color blockquote p{font-size:32px}.wp-block-pullquote.is-style-solid-color .wp-block-pullquote__citation{font-style:normal;text-transform:none}.wp-block-pullquote .wp-block-pullquote__citation{color:inherit}PKt\ijRpullquote/editor.cssnu[.wp-block-pullquote.is-style-solid-color blockquote p{ font-size:32px; } .wp-block-pullquote.is-style-solid-color .wp-block-pullquote__citation{ font-style:normal; text-transform:none; } .wp-block-pullquote .wp-block-pullquote__citation{ color:inherit; }PKt\7-legacy-widget.phpnu[get_widget_key( $id_base ); $widget_object = $wp_widget_factory->get_widget_object( $id_base ); if ( ! $widget_key || ! $widget_object ) { return ''; } if ( isset( $attributes['instance']['encoded'], $attributes['instance']['hash'] ) ) { $serialized_instance = base64_decode( $attributes['instance']['encoded'] ); if ( ! hash_equals( wp_hash( $serialized_instance ), (string) $attributes['instance']['hash'] ) ) { return ''; } $instance = unserialize( $serialized_instance ); } else { $instance = array(); } $args = array( 'widget_id' => $widget_object->id, 'widget_name' => $widget_object->name, ); ob_start(); the_widget( $widget_key, $instance, $args ); return ob_get_clean(); } /** * Registers the 'core/legacy-widget' block. * * @since 5.8.0 */ function register_block_core_legacy_widget() { register_block_type_from_metadata( __DIR__ . '/legacy-widget', array( 'render_callback' => 'render_block_core_legacy_widget', ) ); } add_action( 'init', 'register_block_core_legacy_widget' ); /** * Intercepts any request with legacy-widget-preview in the query param and, if * set, renders a page containing a preview of the requested Legacy Widget * block. * * @since 5.8.0 */ function handle_legacy_widget_preview_iframe() { if ( empty( $_GET['legacy-widget-preview'] ) ) { return; } if ( ! current_user_can( 'edit_theme_options' ) ) { return; } define( 'IFRAME_REQUEST', true ); ?> > >
            get_registered( 'core/legacy-widget' ); echo $block->render( $_GET['legacy-widget-preview'] ); ?>
            li{overflow:hidden}.wp-block-latest-posts li a>div{display:inline}:root :where(.wp-block-latest-posts){padding-left:2.5em}:root :where(.wp-block-latest-posts.is-grid),:root :where(.wp-block-latest-posts__list){padding-left:0}PKt\Z--latest-posts/style.min.cssnu[.wp-block-latest-posts{box-sizing:border-box}.wp-block-latest-posts.alignleft{margin-right:2em}.wp-block-latest-posts.alignright{margin-left:2em}.wp-block-latest-posts.wp-block-latest-posts__list{list-style:none}.wp-block-latest-posts.wp-block-latest-posts__list li{clear:both;overflow-wrap:break-word}.wp-block-latest-posts.is-grid{display:flex;flex-wrap:wrap}.wp-block-latest-posts.is-grid li{margin:0 1.25em 1.25em 0;width:100%}@media (min-width:600px){.wp-block-latest-posts.columns-2 li{width:calc(50% - .625em)}.wp-block-latest-posts.columns-2 li:nth-child(2n){margin-right:0}.wp-block-latest-posts.columns-3 li{width:calc(33.33333% - .83333em)}.wp-block-latest-posts.columns-3 li:nth-child(3n){margin-right:0}.wp-block-latest-posts.columns-4 li{width:calc(25% - .9375em)}.wp-block-latest-posts.columns-4 li:nth-child(4n){margin-right:0}.wp-block-latest-posts.columns-5 li{width:calc(20% - 1em)}.wp-block-latest-posts.columns-5 li:nth-child(5n){margin-right:0}.wp-block-latest-posts.columns-6 li{width:calc(16.66667% - 1.04167em)}.wp-block-latest-posts.columns-6 li:nth-child(6n){margin-right:0}}:root :where(.wp-block-latest-posts.is-grid){padding:0}:root :where(.wp-block-latest-posts.wp-block-latest-posts__list){padding-left:0}.wp-block-latest-posts__post-author,.wp-block-latest-posts__post-date{display:block;font-size:.8125em}.wp-block-latest-posts__post-excerpt,.wp-block-latest-posts__post-full-content{margin-bottom:1em;margin-top:.5em}.wp-block-latest-posts__featured-image a{display:inline-block}.wp-block-latest-posts__featured-image img{height:auto;max-width:100%;width:auto}.wp-block-latest-posts__featured-image.alignleft{float:left;margin-right:1em}.wp-block-latest-posts__featured-image.alignright{float:right;margin-left:1em}.wp-block-latest-posts__featured-image.aligncenter{margin-bottom:1em;text-align:center}PKt\!$$latest-posts/style-rtl.cssnu[.wp-block-latest-posts{ box-sizing:border-box; } .wp-block-latest-posts.alignleft{ margin-right:2em; } .wp-block-latest-posts.alignright{ margin-left:2em; } .wp-block-latest-posts.wp-block-latest-posts__list{ list-style:none; } .wp-block-latest-posts.wp-block-latest-posts__list li{ clear:both; overflow-wrap:break-word; } .wp-block-latest-posts.is-grid{ display:flex; flex-wrap:wrap; } .wp-block-latest-posts.is-grid li{ margin:0 0 1.25em 1.25em; width:100%; } @media (min-width:600px){ .wp-block-latest-posts.columns-2 li{ width:calc(50% - .625em); } .wp-block-latest-posts.columns-2 li:nth-child(2n){ margin-left:0; } .wp-block-latest-posts.columns-3 li{ width:calc(33.33333% - .83333em); } .wp-block-latest-posts.columns-3 li:nth-child(3n){ margin-left:0; } .wp-block-latest-posts.columns-4 li{ width:calc(25% - .9375em); } .wp-block-latest-posts.columns-4 li:nth-child(4n){ margin-left:0; } .wp-block-latest-posts.columns-5 li{ width:calc(20% - 1em); } .wp-block-latest-posts.columns-5 li:nth-child(5n){ margin-left:0; } .wp-block-latest-posts.columns-6 li{ width:calc(16.66667% - 1.04167em); } .wp-block-latest-posts.columns-6 li:nth-child(6n){ margin-left:0; } } :root :where(.wp-block-latest-posts.is-grid){ padding:0; } :root :where(.wp-block-latest-posts.wp-block-latest-posts__list){ padding-right:0; } .wp-block-latest-posts__post-author,.wp-block-latest-posts__post-date{ display:block; font-size:.8125em; } .wp-block-latest-posts__post-excerpt,.wp-block-latest-posts__post-full-content{ margin-bottom:1em; margin-top:.5em; } .wp-block-latest-posts__featured-image a{ display:inline-block; } .wp-block-latest-posts__featured-image img{ height:auto; max-width:100%; width:auto; } .wp-block-latest-posts__featured-image.alignleft{ float:left; margin-right:1em; } .wp-block-latest-posts__featured-image.alignright{ float:right; margin-left:1em; } .wp-block-latest-posts__featured-image.aligncenter{ margin-bottom:1em; text-align:center; }PKt\}GYlatest-posts/editor-rtl.cssnu[.wp-block-latest-posts>li{ overflow:hidden; } .wp-block-latest-posts li a>div{ display:inline; } :root :where(.wp-block-latest-posts){ padding-right:2.5em; } :root :where(.wp-block-latest-posts.is-grid),:root :where(.wp-block-latest-posts__list){ padding-right:0; }PKt\Ē((latest-posts/style.cssnu[.wp-block-latest-posts{ box-sizing:border-box; } .wp-block-latest-posts.alignleft{ margin-right:2em; } .wp-block-latest-posts.alignright{ margin-left:2em; } .wp-block-latest-posts.wp-block-latest-posts__list{ list-style:none; } .wp-block-latest-posts.wp-block-latest-posts__list li{ clear:both; overflow-wrap:break-word; } .wp-block-latest-posts.is-grid{ display:flex; flex-wrap:wrap; } .wp-block-latest-posts.is-grid li{ margin:0 1.25em 1.25em 0; width:100%; } @media (min-width:600px){ .wp-block-latest-posts.columns-2 li{ width:calc(50% - .625em); } .wp-block-latest-posts.columns-2 li:nth-child(2n){ margin-right:0; } .wp-block-latest-posts.columns-3 li{ width:calc(33.33333% - .83333em); } .wp-block-latest-posts.columns-3 li:nth-child(3n){ margin-right:0; } .wp-block-latest-posts.columns-4 li{ width:calc(25% - .9375em); } .wp-block-latest-posts.columns-4 li:nth-child(4n){ margin-right:0; } .wp-block-latest-posts.columns-5 li{ width:calc(20% - 1em); } .wp-block-latest-posts.columns-5 li:nth-child(5n){ margin-right:0; } .wp-block-latest-posts.columns-6 li{ width:calc(16.66667% - 1.04167em); } .wp-block-latest-posts.columns-6 li:nth-child(6n){ margin-right:0; } } :root :where(.wp-block-latest-posts.is-grid){ padding:0; } :root :where(.wp-block-latest-posts.wp-block-latest-posts__list){ padding-left:0; } .wp-block-latest-posts__post-author,.wp-block-latest-posts__post-date{ display:block; font-size:.8125em; } .wp-block-latest-posts__post-excerpt,.wp-block-latest-posts__post-full-content{ margin-bottom:1em; margin-top:.5em; } .wp-block-latest-posts__featured-image a{ display:inline-block; } .wp-block-latest-posts__featured-image img{ height:auto; max-width:100%; width:auto; } .wp-block-latest-posts__featured-image.alignleft{ float:left; margin-right:1em; } .wp-block-latest-posts__featured-image.alignright{ float:right; margin-left:1em; } .wp-block-latest-posts__featured-image.aligncenter{ margin-bottom:1em; text-align:center; }PKt\=.)))latest-posts/style-rtl.min.cssnu[.wp-block-latest-posts{box-sizing:border-box}.wp-block-latest-posts.alignleft{margin-right:2em}.wp-block-latest-posts.alignright{margin-left:2em}.wp-block-latest-posts.wp-block-latest-posts__list{list-style:none}.wp-block-latest-posts.wp-block-latest-posts__list li{clear:both;overflow-wrap:break-word}.wp-block-latest-posts.is-grid{display:flex;flex-wrap:wrap}.wp-block-latest-posts.is-grid li{margin:0 0 1.25em 1.25em;width:100%}@media (min-width:600px){.wp-block-latest-posts.columns-2 li{width:calc(50% - .625em)}.wp-block-latest-posts.columns-2 li:nth-child(2n){margin-left:0}.wp-block-latest-posts.columns-3 li{width:calc(33.33333% - .83333em)}.wp-block-latest-posts.columns-3 li:nth-child(3n){margin-left:0}.wp-block-latest-posts.columns-4 li{width:calc(25% - .9375em)}.wp-block-latest-posts.columns-4 li:nth-child(4n){margin-left:0}.wp-block-latest-posts.columns-5 li{width:calc(20% - 1em)}.wp-block-latest-posts.columns-5 li:nth-child(5n){margin-left:0}.wp-block-latest-posts.columns-6 li{width:calc(16.66667% - 1.04167em)}.wp-block-latest-posts.columns-6 li:nth-child(6n){margin-left:0}}:root :where(.wp-block-latest-posts.is-grid){padding:0}:root :where(.wp-block-latest-posts.wp-block-latest-posts__list){padding-right:0}.wp-block-latest-posts__post-author,.wp-block-latest-posts__post-date{display:block;font-size:.8125em}.wp-block-latest-posts__post-excerpt,.wp-block-latest-posts__post-full-content{margin-bottom:1em;margin-top:.5em}.wp-block-latest-posts__featured-image a{display:inline-block}.wp-block-latest-posts__featured-image img{height:auto;max-width:100%;width:auto}.wp-block-latest-posts__featured-image.alignleft{float:left;margin-right:1em}.wp-block-latest-posts__featured-image.alignright{float:right;margin-left:1em}.wp-block-latest-posts__featured-image.aligncenter{margin-bottom:1em;text-align:center}PKt\z1latest-posts/editor-rtl.min.cssnu[.wp-block-latest-posts>li{overflow:hidden}.wp-block-latest-posts li a>div{display:inline}:root :where(.wp-block-latest-posts){padding-right:2.5em}:root :where(.wp-block-latest-posts.is-grid),:root :where(.wp-block-latest-posts__list){padding-right:0}PKt\Hlatest-posts/editor.cssnu[.wp-block-latest-posts>li{ overflow:hidden; } .wp-block-latest-posts li a>div{ display:inline; } :root :where(.wp-block-latest-posts){ padding-left:2.5em; } :root :where(.wp-block-latest-posts.is-grid),:root :where(.wp-block-latest-posts__list){ padding-left:0; }PKt\2query/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/query", "title": "Query Loop", "category": "theme", "description": "An advanced block that allows displaying post types based on different query parameters and visual configurations.", "keywords": [ "posts", "list", "blog", "blogs", "custom post types" ], "textdomain": "default", "attributes": { "queryId": { "type": "number" }, "query": { "type": "object", "default": { "perPage": null, "pages": 0, "offset": 0, "postType": "post", "order": "desc", "orderBy": "date", "author": "", "search": "", "exclude": [], "sticky": "", "inherit": true, "taxQuery": null, "parents": [], "format": [] } }, "tagName": { "type": "string", "default": "div" }, "namespace": { "type": "string" }, "enhancedPagination": { "type": "boolean", "default": false } }, "usesContext": [ "templateSlug" ], "providesContext": { "queryId": "queryId", "query": "query", "displayLayout": "displayLayout", "enhancedPagination": "enhancedPagination" }, "supports": { "align": [ "wide", "full" ], "html": false, "layout": true, "interactivity": true }, "editorStyle": "wp-block-query-editor" } PKt\query/editor.min.cssnu[.block-library-query-pattern__selection-modal .block-editor-block-patterns-list{column-count:2;column-gap:24px}@media (min-width:1280px){.block-library-query-pattern__selection-modal .block-editor-block-patterns-list{column-count:3}}.block-library-query-pattern__selection-modal .block-editor-block-patterns-list .block-editor-block-patterns-list__list-item{break-inside:avoid-column}.block-library-query-pattern__selection-modal .block-library-query-pattern__selection-search{background:#fff;margin-bottom:-4px;padding:16px 0;position:sticky;top:0;transform:translateY(-4px);z-index:2}@media (min-width:600px){.wp-block-query__enhanced-pagination-modal{max-width:480px}}.block-editor-block-settings-menu__popover.is-expanded{overflow-y:scroll}.block-editor-block-settings-menu__popover .block-library-query-pattern__selection-content{height:100%}.block-editor-block-settings-menu__popover .block-editor-block-patterns-list{display:grid;grid-template-columns:1fr;grid-gap:12px;min-width:280px}@media (min-width:600px){.block-editor-block-settings-menu__popover .block-editor-block-patterns-list{grid-template-columns:1fr 1fr;min-width:480px}}.block-editor-block-settings-menu__popover .block-editor-block-patterns-list__list-item{margin-bottom:0}PKt\TTquery/view.min.asset.phpnu[ array(), 'version' => '490915f92cc794ea16e1'); PKt\ query/view.jsnu[import * as __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__ from "@wordpress/interactivity"; /******/ var __webpack_modules__ = ({ /***/ 438: /***/ ((module) => { module.exports = import("@wordpress/interactivity-router");; /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; ;// external "@wordpress/interactivity" var x = (y) => { var x = {}; __webpack_require__.d(x, y); return x } var y = (x) => (() => (x)) const interactivity_namespaceObject = x({ ["getContext"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getContext), ["getElement"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getElement), ["store"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.store), ["withSyncEvent"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.withSyncEvent) }); ;// ./node_modules/@wordpress/block-library/build-module/query/view.js /** * WordPress dependencies */ const isValidLink = ref => ref && ref instanceof window.HTMLAnchorElement && ref.href && (!ref.target || ref.target === '_self') && ref.origin === window.location.origin; const isValidEvent = event => event.button === 0 && // Left clicks only. !event.metaKey && // Open in new tab (Mac). !event.ctrlKey && // Open in new tab (Windows). !event.altKey && // Download. !event.shiftKey && !event.defaultPrevented; (0,interactivity_namespaceObject.store)('core/query', { actions: { navigate: (0,interactivity_namespaceObject.withSyncEvent)(function* (event) { const ctx = (0,interactivity_namespaceObject.getContext)(); const { ref } = (0,interactivity_namespaceObject.getElement)(); const queryRef = ref.closest('.wp-block-query[data-wp-router-region]'); if (isValidLink(ref) && isValidEvent(event)) { event.preventDefault(); const { actions } = yield Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 438)); yield actions.navigate(ref.href); ctx.url = ref.href; // Focus the first anchor of the Query block. const firstAnchor = `.wp-block-post-template a[href]`; queryRef.querySelector(firstAnchor)?.focus(); } }), *prefetch() { const { ref } = (0,interactivity_namespaceObject.getElement)(); if (isValidLink(ref)) { const { actions } = yield Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 438)); yield actions.prefetch(ref.href); } } }, callbacks: { *prefetch() { const { url } = (0,interactivity_namespaceObject.getContext)(); const { ref } = (0,interactivity_namespaceObject.getElement)(); if (url && isValidLink(ref)) { const { actions } = yield Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 438)); yield actions.prefetch(ref.href); } } } }, { lock: true }); PKt\TTquery/view.asset.phpnu[ array(), 'version' => 'ee101e08820687c9c07f'); PKt\w96VVquery/editor-rtl.cssnu[.block-library-query-pattern__selection-modal .block-editor-block-patterns-list{ column-count:2; column-gap:24px; } @media (min-width:1280px){ .block-library-query-pattern__selection-modal .block-editor-block-patterns-list{ column-count:3; } } .block-library-query-pattern__selection-modal .block-editor-block-patterns-list .block-editor-block-patterns-list__list-item{ break-inside:avoid-column; } .block-library-query-pattern__selection-modal .block-library-query-pattern__selection-search{ background:#fff; margin-bottom:-4px; padding:16px 0; position:sticky; top:0; transform:translateY(-4px); z-index:2; } @media (min-width:600px){ .wp-block-query__enhanced-pagination-modal{ max-width:480px; } } .block-editor-block-settings-menu__popover.is-expanded{ overflow-y:scroll; } .block-editor-block-settings-menu__popover .block-library-query-pattern__selection-content{ height:100%; } .block-editor-block-settings-menu__popover .block-editor-block-patterns-list{ display:grid; grid-template-columns:1fr; grid-gap:12px; min-width:280px; } @media (min-width:600px){ .block-editor-block-settings-menu__popover .block-editor-block-patterns-list{ grid-template-columns:1fr 1fr; min-width:480px; } } .block-editor-block-settings-menu__popover .block-editor-block-patterns-list__list-item{ margin-bottom:0; }PKt\$query/view.min.jsnu[import*as e from"@wordpress/interactivity";var t={438:e=>{e.exports=import("@wordpress/interactivity-router")}},r={};function o(e){var n=r[e];if(void 0!==n)return n.exports;var i=r[e]={exports:{}};return t[e](i,i.exports,o),i.exports}o.d=(e,t)=>{for(var r in t)o.o(t,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);const n=(e=>{var t={};return o.d(t,e),t})({getContext:()=>e.getContext,getElement:()=>e.getElement,store:()=>e.store,withSyncEvent:()=>e.withSyncEvent}),i=e=>e&&e instanceof window.HTMLAnchorElement&&e.href&&(!e.target||"_self"===e.target)&&e.origin===window.location.origin;(0,n.store)("core/query",{actions:{navigate:(0,n.withSyncEvent)((function*(e){const t=(0,n.getContext)(),{ref:r}=(0,n.getElement)(),s=r.closest(".wp-block-query[data-wp-router-region]");if(i(r)&&(e=>!(0!==e.button||e.metaKey||e.ctrlKey||e.altKey||e.shiftKey||e.defaultPrevented))(e)){e.preventDefault();const{actions:n}=yield Promise.resolve().then(o.bind(o,438));yield n.navigate(r.href),t.url=r.href;const i=".wp-block-post-template a[href]";s.querySelector(i)?.focus()}})),*prefetch(){const{ref:e}=(0,n.getElement)();if(i(e)){const{actions:t}=yield Promise.resolve().then(o.bind(o,438));yield t.prefetch(e.href)}}},callbacks:{*prefetch(){const{url:e}=(0,n.getContext)(),{ref:t}=(0,n.getElement)();if(e&&i(t)){const{actions:e}=yield Promise.resolve().then(o.bind(o,438));yield e.prefetch(t.href)}}}},{lock:!0});PKt\query/editor-rtl.min.cssnu[.block-library-query-pattern__selection-modal .block-editor-block-patterns-list{column-count:2;column-gap:24px}@media (min-width:1280px){.block-library-query-pattern__selection-modal .block-editor-block-patterns-list{column-count:3}}.block-library-query-pattern__selection-modal .block-editor-block-patterns-list .block-editor-block-patterns-list__list-item{break-inside:avoid-column}.block-library-query-pattern__selection-modal .block-library-query-pattern__selection-search{background:#fff;margin-bottom:-4px;padding:16px 0;position:sticky;top:0;transform:translateY(-4px);z-index:2}@media (min-width:600px){.wp-block-query__enhanced-pagination-modal{max-width:480px}}.block-editor-block-settings-menu__popover.is-expanded{overflow-y:scroll}.block-editor-block-settings-menu__popover .block-library-query-pattern__selection-content{height:100%}.block-editor-block-settings-menu__popover .block-editor-block-patterns-list{display:grid;grid-template-columns:1fr;grid-gap:12px;min-width:280px}@media (min-width:600px){.block-editor-block-settings-menu__popover .block-editor-block-patterns-list{grid-template-columns:1fr 1fr;min-width:480px}}.block-editor-block-settings-menu__popover .block-editor-block-patterns-list__list-item{margin-bottom:0}PKt\w96VVquery/editor.cssnu[.block-library-query-pattern__selection-modal .block-editor-block-patterns-list{ column-count:2; column-gap:24px; } @media (min-width:1280px){ .block-library-query-pattern__selection-modal .block-editor-block-patterns-list{ column-count:3; } } .block-library-query-pattern__selection-modal .block-editor-block-patterns-list .block-editor-block-patterns-list__list-item{ break-inside:avoid-column; } .block-library-query-pattern__selection-modal .block-library-query-pattern__selection-search{ background:#fff; margin-bottom:-4px; padding:16px 0; position:sticky; top:0; transform:translateY(-4px); z-index:2; } @media (min-width:600px){ .wp-block-query__enhanced-pagination-modal{ max-width:480px; } } .block-editor-block-settings-menu__popover.is-expanded{ overflow-y:scroll; } .block-editor-block-settings-menu__popover .block-library-query-pattern__selection-content{ height:100%; } .block-editor-block-settings-menu__popover .block-editor-block-patterns-list{ display:grid; grid-template-columns:1fr; grid-gap:12px; min-width:280px; } @media (min-width:600px){ .block-editor-block-settings-menu__popover .block-editor-block-patterns-list{ grid-template-columns:1fr 1fr; min-width:480px; } } .block-editor-block-settings-menu__popover .block-editor-block-patterns-list__list-item{ margin-bottom:0; }PKt\ block.phpnu[post_type ) { return ''; } if ( isset( $seen_refs[ $attributes['ref'] ] ) ) { // WP_DEBUG_DISPLAY must only be honored when WP_DEBUG. This precedent // is set in `wp_debug_mode()`. $is_debug = WP_DEBUG && WP_DEBUG_DISPLAY; return $is_debug ? // translators: Visible only in the front end, this warning takes the place of a faulty block. __( '[block rendering halted]' ) : ''; } if ( 'publish' !== $reusable_block->post_status || ! empty( $reusable_block->post_password ) ) { return ''; } $seen_refs[ $attributes['ref'] ] = true; // Handle embeds for reusable blocks. global $wp_embed; $content = $wp_embed->run_shortcode( $reusable_block->post_content ); $content = $wp_embed->autoembed( $content ); // Back compat. // For blocks that have not been migrated in the editor, add some back compat // so that front-end rendering continues to work. // This matches the `v2` deprecation. Removes the inner `values` property // from every item. if ( isset( $attributes['content'] ) ) { foreach ( $attributes['content'] as &$content_data ) { if ( isset( $content_data['values'] ) ) { $is_assoc_array = is_array( $content_data['values'] ) && ! wp_is_numeric_array( $content_data['values'] ); if ( $is_assoc_array ) { $content_data = $content_data['values']; } } } } // This matches the `v1` deprecation. Rename `overrides` to `content`. if ( isset( $attributes['overrides'] ) && ! isset( $attributes['content'] ) ) { $attributes['content'] = $attributes['overrides']; } /** * We set the `pattern/overrides` context through the `render_block_context` * filter so that it is available when a pattern's inner blocks are * rendering via do_blocks given it only receives the inner content. */ $has_pattern_overrides = isset( $attributes['content'] ) && null !== get_block_bindings_source( 'core/pattern-overrides' ); if ( $has_pattern_overrides ) { $filter_block_context = static function ( $context ) use ( $attributes ) { $context['pattern/overrides'] = $attributes['content']; return $context; }; add_filter( 'render_block_context', $filter_block_context, 1 ); } // Apply Block Hooks. $content = apply_block_hooks_to_content_from_post_object( $content, $reusable_block ); $content = do_blocks( $content ); unset( $seen_refs[ $attributes['ref'] ] ); if ( $has_pattern_overrides ) { remove_filter( 'render_block_context', $filter_block_context, 1 ); } return $content; } /** * Registers the `core/block` block. * * @since 5.3.0 */ function register_block_core_block() { register_block_type_from_metadata( __DIR__ . '/block', array( 'render_callback' => 'render_block_core_block', ) ); } add_action( 'init', 'register_block_core_block' ); PKt\y+comment-reply-link/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/comment-reply-link", "title": "Comment Reply Link", "category": "theme", "ancestor": [ "core/comment-template" ], "description": "Displays a link to reply to a comment.", "textdomain": "default", "usesContext": [ "commentId" ], "attributes": { "textAlign": { "type": "string" } }, "supports": { "color": { "gradients": true, "link": true, "text": false, "__experimentalDefaultControls": { "background": true, "link": true } }, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true }, "html": false }, "style": "wp-block-comment-reply-link" } PKt\'qq33 comment-reply-link/style.min.cssnu[.wp-block-comment-reply-link{box-sizing:border-box}PKt\!ޡ88 comment-reply-link/style-rtl.cssnu[.wp-block-comment-reply-link{ box-sizing:border-box; }PKt\!ޡ88comment-reply-link/style.cssnu[.wp-block-comment-reply-link{ box-sizing:border-box; }PKt\'qq33$comment-reply-link/style-rtl.min.cssnu[.wp-block-comment-reply-link{box-sizing:border-box}PKt\Kcomments-pagination-next.phpnu[context['postId'] ) ) { return ''; } $comment_vars = build_comment_query_vars_from_block( $block ); $max_page = ( new WP_Comment_Query( $comment_vars ) )->max_num_pages; $default_label = __( 'Newer Comments' ); $label = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? $attributes['label'] : $default_label; $pagination_arrow = get_comments_pagination_arrow( $block, 'next' ); $filter_link_attributes = static function () { return get_block_wrapper_attributes(); }; add_filter( 'next_comments_link_attributes', $filter_link_attributes ); if ( $pagination_arrow ) { $label .= $pagination_arrow; } $next_comments_link = get_next_comments_link( $label, $max_page, $comment_vars['paged'] ?? null ); remove_filter( 'next_posts_link_attributes', $filter_link_attributes ); if ( ! isset( $next_comments_link ) ) { return ''; } return $next_comments_link; } /** * Registers the `core/comments-pagination-next` block on the server. * * @since 6.0.0 */ function register_block_core_comments_pagination_next() { register_block_type_from_metadata( __DIR__ . '/comments-pagination-next', array( 'render_callback' => 'render_block_core_comments_pagination_next', ) ); } add_action( 'init', 'register_block_core_comments_pagination_next' ); PKt\5Ctext-columns/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/text-columns", "title": "Text Columns (deprecated)", "icon": "columns", "category": "design", "description": "This block is deprecated. Please use the Columns block instead.", "textdomain": "default", "attributes": { "content": { "type": "array", "source": "query", "selector": "p", "query": { "children": { "type": "string", "source": "html" } }, "default": [ {}, {} ] }, "columns": { "type": "number", "default": 2 }, "width": { "type": "string" } }, "supports": { "inserter": false, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-text-columns-editor", "style": "wp-block-text-columns" } PKt\ VVtext-columns/editor.min.cssnu[.wp-block-text-columns .block-editor-rich-text__editable:focus{outline:1px solid #ddd}PKt\H sytext-columns/style.min.cssnu[.wp-block-text-columns,.wp-block-text-columns.aligncenter{display:flex}.wp-block-text-columns .wp-block-column{margin:0 1em;padding:0}.wp-block-text-columns .wp-block-column:first-child{margin-left:0}.wp-block-text-columns .wp-block-column:last-child{margin-right:0}.wp-block-text-columns.columns-2 .wp-block-column{width:50%}.wp-block-text-columns.columns-3 .wp-block-column{width:33.3333333333%}.wp-block-text-columns.columns-4 .wp-block-column{width:25%}PKt\text-columns/style-rtl.cssnu[.wp-block-text-columns,.wp-block-text-columns.aligncenter{ display:flex; } .wp-block-text-columns .wp-block-column{ margin:0 1em; padding:0; } .wp-block-text-columns .wp-block-column:first-child{ margin-right:0; } .wp-block-text-columns .wp-block-column:last-child{ margin-left:0; } .wp-block-text-columns.columns-2 .wp-block-column{ width:50%; } .wp-block-text-columns.columns-3 .wp-block-column{ width:33.3333333333%; } .wp-block-text-columns.columns-4 .wp-block-column{ width:25%; }PKt\Z[[text-columns/editor-rtl.cssnu[.wp-block-text-columns .block-editor-rich-text__editable:focus{ outline:1px solid #ddd; }PKt\cetext-columns/style.cssnu[.wp-block-text-columns,.wp-block-text-columns.aligncenter{ display:flex; } .wp-block-text-columns .wp-block-column{ margin:0 1em; padding:0; } .wp-block-text-columns .wp-block-column:first-child{ margin-left:0; } .wp-block-text-columns .wp-block-column:last-child{ margin-right:0; } .wp-block-text-columns.columns-2 .wp-block-column{ width:50%; } .wp-block-text-columns.columns-3 .wp-block-column{ width:33.3333333333%; } .wp-block-text-columns.columns-4 .wp-block-column{ width:25%; }PKt\A8text-columns/style-rtl.min.cssnu[.wp-block-text-columns,.wp-block-text-columns.aligncenter{display:flex}.wp-block-text-columns .wp-block-column{margin:0 1em;padding:0}.wp-block-text-columns .wp-block-column:first-child{margin-right:0}.wp-block-text-columns .wp-block-column:last-child{margin-left:0}.wp-block-text-columns.columns-2 .wp-block-column{width:50%}.wp-block-text-columns.columns-3 .wp-block-column{width:33.3333333333%}.wp-block-text-columns.columns-4 .wp-block-column{width:25%}PKt\ VVtext-columns/editor-rtl.min.cssnu[.wp-block-text-columns .block-editor-rich-text__editable:focus{outline:1px solid #ddd}PKt\Z[[text-columns/editor.cssnu[.wp-block-text-columns .block-editor-rich-text__editable:focus{ outline:1px solid #ddd; }PKt\6 loginout.phpnu[ false ) ); } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) ); return '
            ' . $contents . '
            '; } /** * Registers the `core/loginout` block on server. * * @since 5.8.0 */ function register_block_core_loginout() { register_block_type_from_metadata( __DIR__ . '/loginout', array( 'render_callback' => 'render_block_core_loginout', ) ); } add_action( 'init', 'register_block_core_loginout' ); PKt\&^dcalendar/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/calendar", "title": "Calendar", "category": "widgets", "description": "A calendar of your site’s posts.", "keywords": [ "posts", "archive" ], "textdomain": "default", "attributes": { "month": { "type": "integer" }, "year": { "type": "integer" } }, "supports": { "align": true, "color": { "link": true, "__experimentalSkipSerialization": [ "text", "background" ], "__experimentalDefaultControls": { "background": true, "text": true }, "__experimentalSelector": "table, th" }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } }, "style": "wp-block-calendar" } PKt\qcalendar/style.min.cssnu[.wp-block-calendar{text-align:center}.wp-block-calendar td,.wp-block-calendar th{border:1px solid;padding:.25em}.wp-block-calendar th{font-weight:400}.wp-block-calendar caption{background-color:inherit}.wp-block-calendar table{border-collapse:collapse;width:100%}.wp-block-calendar table:where(:not(.has-text-color)){color:#40464d}.wp-block-calendar table:where(:not(.has-text-color)) td,.wp-block-calendar table:where(:not(.has-text-color)) th{border-color:#ddd}.wp-block-calendar table.has-background th{background-color:inherit}.wp-block-calendar table.has-text-color th{color:inherit}:where(.wp-block-calendar table:not(.has-background) th){background:#ddd}PKt\Tkkmcalendar/style-rtl.cssnu[.wp-block-calendar{ text-align:center; } .wp-block-calendar td,.wp-block-calendar th{ border:1px solid; padding:.25em; } .wp-block-calendar th{ font-weight:400; } .wp-block-calendar caption{ background-color:inherit; } .wp-block-calendar table{ border-collapse:collapse; width:100%; } .wp-block-calendar table:where(:not(.has-text-color)){ color:#40464d; } .wp-block-calendar table:where(:not(.has-text-color)) td,.wp-block-calendar table:where(:not(.has-text-color)) th{ border-color:#ddd; } .wp-block-calendar table.has-background th{ background-color:inherit; } .wp-block-calendar table.has-text-color th{ color:inherit; } :where(.wp-block-calendar table:not(.has-background) th){ background:#ddd; }PKt\Tkkmcalendar/style.cssnu[.wp-block-calendar{ text-align:center; } .wp-block-calendar td,.wp-block-calendar th{ border:1px solid; padding:.25em; } .wp-block-calendar th{ font-weight:400; } .wp-block-calendar caption{ background-color:inherit; } .wp-block-calendar table{ border-collapse:collapse; width:100%; } .wp-block-calendar table:where(:not(.has-text-color)){ color:#40464d; } .wp-block-calendar table:where(:not(.has-text-color)) td,.wp-block-calendar table:where(:not(.has-text-color)) th{ border-color:#ddd; } .wp-block-calendar table.has-background th{ background-color:inherit; } .wp-block-calendar table.has-text-color th{ color:inherit; } :where(.wp-block-calendar table:not(.has-background) th){ background:#ddd; }PKt\qcalendar/style-rtl.min.cssnu[.wp-block-calendar{text-align:center}.wp-block-calendar td,.wp-block-calendar th{border:1px solid;padding:.25em}.wp-block-calendar th{font-weight:400}.wp-block-calendar caption{background-color:inherit}.wp-block-calendar table{border-collapse:collapse;width:100%}.wp-block-calendar table:where(:not(.has-text-color)){color:#40464d}.wp-block-calendar table:where(:not(.has-text-color)) td,.wp-block-calendar table:where(:not(.has-text-color)) th{border-color:#ddd}.wp-block-calendar table.has-background th{background-color:inherit}.wp-block-calendar table.has-text-color th{color:inherit}:where(.wp-block-calendar table:not(.has-background) th){background:#ddd}PKt\Y;$88term-description.phpnu[ implode( ' ', $classes ) ) ); return '
            ' . $term_description . '
            '; } /** * Registers the `core/term-description` block on the server. * * @since 5.9.0 */ function register_block_core_term_description() { register_block_type_from_metadata( __DIR__ . '/term-description', array( 'render_callback' => 'render_block_core_term_description', ) ); } add_action( 'init', 'register_block_core_term_description' ); PKt\Ȃ post-comments-form.phpnu[context['postId'] ) ) { return ''; } if ( post_password_required( $block->context['postId'] ) ) { return; } $classes = array( 'comment-respond' ); // See comment further below. if ( isset( $attributes['textAlign'] ) ) { $classes[] = 'has-text-align-' . $attributes['textAlign']; } if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { $classes[] = 'has-link-color'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) ); add_filter( 'comment_form_defaults', 'post_comments_form_block_form_defaults' ); ob_start(); comment_form( array(), $block->context['postId'] ); $form = ob_get_clean(); remove_filter( 'comment_form_defaults', 'post_comments_form_block_form_defaults' ); // We use the outermost wrapping `
            ` returned by `comment_form()` // which is identified by its default classname `comment-respond` to inject // our wrapper attributes. This way, it is guaranteed that all styling applied // to the block is carried along when the comment form is moved to the location // of the 'Reply' link that the user clicked by Core's `comment-reply.js` script. $form = str_replace( 'class="comment-respond"', $wrapper_attributes, $form ); // Enqueue the comment-reply script. wp_enqueue_script( 'comment-reply' ); return $form; } /** * Registers the `core/post-comments-form` block on the server. * * @since 6.0.0 */ function register_block_core_post_comments_form() { register_block_type_from_metadata( __DIR__ . '/post-comments-form', array( 'render_callback' => 'render_block_core_post_comments_form', ) ); } add_action( 'init', 'register_block_core_post_comments_form' ); /** * Use the button block classes for the form-submit button. * * @since 6.0.0 * * @param array $fields The default comment form arguments. * * @return array Returns the modified fields. */ function post_comments_form_block_form_defaults( $fields ) { if ( wp_is_block_theme() ) { $fields['submit_button'] = ''; $fields['submit_field'] = '

            %1$s %2$s

            '; } return $fields; } PKt\site-title/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/site-title", "title": "Site Title", "category": "theme", "description": "Displays the name of this site. Update the block, and the changes apply everywhere it’s used. This will also appear in the browser title bar and in search results.", "textdomain": "default", "attributes": { "level": { "type": "number", "default": 1 }, "levelOptions": { "type": "array", "default": [ 0, 1, 2, 3, 4, 5, 6 ] }, "textAlign": { "type": "string" }, "isLink": { "type": "boolean", "default": true, "role": "content" }, "linkTarget": { "type": "string", "default": "_self", "role": "content" } }, "example": { "viewportWidth": 500 }, "supports": { "align": [ "wide", "full" ], "html": false, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true, "link": true } }, "spacing": { "padding": true, "margin": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalFontStyle": true, "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, "__experimentalWritingMode": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true } }, "editorStyle": "wp-block-site-title-editor", "style": "wp-block-site-title" } PKt\fBBsite-title/editor.min.cssnu[.wp-block-site-title__placeholder{border:1px dashed;padding:1em 0}PKt\Csite-title/style.min.cssnu[.wp-block-site-title{box-sizing:border-box}.wp-block-site-title :where(a){color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;text-decoration:inherit}PKt\6Rsite-title/style-rtl.cssnu[.wp-block-site-title{ box-sizing:border-box; } .wp-block-site-title :where(a){ color:inherit; font-family:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; letter-spacing:inherit; line-height:inherit; text-decoration:inherit; }PKt\gZJJsite-title/editor-rtl.cssnu[.wp-block-site-title__placeholder{ border:1px dashed; padding:1em 0; }PKt\6Rsite-title/style.cssnu[.wp-block-site-title{ box-sizing:border-box; } .wp-block-site-title :where(a){ color:inherit; font-family:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; letter-spacing:inherit; line-height:inherit; text-decoration:inherit; }PKt\Csite-title/style-rtl.min.cssnu[.wp-block-site-title{box-sizing:border-box}.wp-block-site-title :where(a){color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;text-decoration:inherit}PKt\fBBsite-title/editor-rtl.min.cssnu[.wp-block-site-title__placeholder{border:1px dashed;padding:1em 0}PKt\gZJJsite-title/editor.cssnu[.wp-block-site-title__placeholder{ border:1px dashed; padding:1em 0; }PKt\"$# widget-group.phpnu['; $after_title = ''; } $html = ''; if ( ! empty( $attributes['title'] ) ) { $html .= $before_title . esc_html( $attributes['title'] ) . $after_title; } $html .= '
            '; foreach ( $block->inner_blocks as $inner_block ) { $html .= $inner_block->render(); } $html .= '
            '; return $html; } /** * Registers the 'core/widget-group' block. * * @since 5.9.0 */ function register_block_core_widget_group() { register_block_type_from_metadata( __DIR__ . '/widget-group', array( 'render_callback' => 'render_block_core_widget_group', ) ); } add_action( 'init', 'register_block_core_widget_group' ); /** * Make a note of the sidebar being rendered before WordPress starts rendering * it. This lets us get to the current sidebar in * render_block_core_widget_group(). * * @since 5.9.0 * * @global int|string $_sidebar_being_rendered * * @param int|string $index Index, name, or ID of the dynamic sidebar. */ function note_sidebar_being_rendered( $index ) { global $_sidebar_being_rendered; $_sidebar_being_rendered = $index; } add_action( 'dynamic_sidebar_before', 'note_sidebar_being_rendered' ); /** * Clear whatever we set in note_sidebar_being_rendered() after WordPress * finishes rendering a sidebar. * * @since 5.9.0 * * @global int|string $_sidebar_being_rendered */ function discard_sidebar_being_rendered() { global $_sidebar_being_rendered; unset( $_sidebar_being_rendered ); } add_action( 'dynamic_sidebar_after', 'discard_sidebar_being_rendered' ); PKt\)comment-template/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/comment-template", "title": "Comment Template", "category": "design", "parent": [ "core/comments" ], "description": "Contains the block elements used to display a comment, like the title, date, author, avatar and more.", "textdomain": "default", "usesContext": [ "postId" ], "supports": { "align": true, "html": false, "reusable": false, "spacing": { "margin": true, "padding": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "style": "wp-block-comment-template" } PKt\8˰comment-template/style.min.cssnu[.wp-block-comment-template{box-sizing:border-box;list-style:none;margin-bottom:0;max-width:100%;padding:0}.wp-block-comment-template li{clear:both}.wp-block-comment-template ol{list-style:none;margin-bottom:0;max-width:100%;padding-left:2rem}.wp-block-comment-template.alignleft{float:left}.wp-block-comment-template.aligncenter{margin-left:auto;margin-right:auto;width:fit-content}.wp-block-comment-template.alignright{float:right}PKt\zcomment-template/style-rtl.cssnu[.wp-block-comment-template{ box-sizing:border-box; list-style:none; margin-bottom:0; max-width:100%; padding:0; } .wp-block-comment-template li{ clear:both; } .wp-block-comment-template ol{ list-style:none; margin-bottom:0; max-width:100%; padding-right:2rem; } .wp-block-comment-template.alignleft{ float:right; } .wp-block-comment-template.aligncenter{ margin-left:auto; margin-right:auto; width:fit-content; } .wp-block-comment-template.alignright{ float:left; }PKt\iGcomment-template/style.cssnu[.wp-block-comment-template{ box-sizing:border-box; list-style:none; margin-bottom:0; max-width:100%; padding:0; } .wp-block-comment-template li{ clear:both; } .wp-block-comment-template ol{ list-style:none; margin-bottom:0; max-width:100%; padding-left:2rem; } .wp-block-comment-template.alignleft{ float:left; } .wp-block-comment-template.aligncenter{ margin-left:auto; margin-right:auto; width:fit-content; } .wp-block-comment-template.alignright{ float:right; }PKt\}o"comment-template/style-rtl.min.cssnu[.wp-block-comment-template{box-sizing:border-box;list-style:none;margin-bottom:0;max-width:100%;padding:0}.wp-block-comment-template li{clear:both}.wp-block-comment-template ol{list-style:none;margin-bottom:0;max-width:100%;padding-right:2rem}.wp-block-comment-template.alignleft{float:right}.wp-block-comment-template.aligncenter{margin-left:auto;margin-right:auto;width:fit-content}.wp-block-comment-template.alignright{float:left}PKt\Lа:: query.phpnu[next_tag() ) { // Add the necessary directives. $p->set_attribute( 'data-wp-interactive', 'core/query' ); $p->set_attribute( 'data-wp-router-region', 'query-' . $attributes['queryId'] ); $p->set_attribute( 'data-wp-context', '{}' ); $p->set_attribute( 'data-wp-key', $attributes['queryId'] ); $content = $p->get_updated_html(); } } // Add the styles to the block type if the block is interactive and remove // them if it's not. $style_asset = 'wp-block-query'; if ( ! wp_style_is( $style_asset ) ) { $style_handles = $block->block_type->style_handles; // If the styles are not needed, and they are still in the `style_handles`, remove them. if ( ! $is_interactive && in_array( $style_asset, $style_handles, true ) ) { $block->block_type->style_handles = array_diff( $style_handles, array( $style_asset ) ); } // If the styles are needed, but they were previously removed, add them again. if ( $is_interactive && ! in_array( $style_asset, $style_handles, true ) ) { $block->block_type->style_handles = array_merge( $style_handles, array( $style_asset ) ); } } return $content; } /** * Registers the `core/query` block on the server. * * @since 5.8.0 */ function register_block_core_query() { register_block_type_from_metadata( __DIR__ . '/query', array( 'render_callback' => 'render_block_core_query', ) ); } add_action( 'init', 'register_block_core_query' ); /** * Traverse the tree of blocks looking for any plugin block (i.e., a block from * an installed plugin) inside a Query block with the enhanced pagination * enabled. If at least one is found, the enhanced pagination is effectively * disabled to prevent any potential incompatibilities. * * @since 6.4.0 * * @param array $parsed_block The block being rendered. * @return array Returns the parsed block, unmodified. */ function block_core_query_disable_enhanced_pagination( $parsed_block ) { static $enhanced_query_stack = array(); static $dirty_enhanced_queries = array(); static $render_query_callback = null; $block_name = $parsed_block['blockName']; $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); $has_enhanced_pagination = isset( $parsed_block['attrs']['enhancedPagination'] ) && true === $parsed_block['attrs']['enhancedPagination'] && isset( $parsed_block['attrs']['queryId'] ); /* * Client side navigation can be true in two states: * - supports.interactivity = true; * - supports.interactivity.clientNavigation = true; */ $supports_client_navigation = ( isset( $block_type->supports['interactivity']['clientNavigation'] ) && true === $block_type->supports['interactivity']['clientNavigation'] ) || ( isset( $block_type->supports['interactivity'] ) && true === $block_type->supports['interactivity'] ); if ( 'core/query' === $block_name && $has_enhanced_pagination ) { $enhanced_query_stack[] = $parsed_block['attrs']['queryId']; if ( ! isset( $render_query_callback ) ) { /** * Filter that disables the enhanced pagination feature during block * rendering when a plugin block has been found inside. It does so * by adding an attribute called `data-wp-navigation-disabled` which * is later handled by the front-end logic. * * @param string $content The block content. * @param array $block The full block, including name and attributes. * @return string Returns the modified output of the query block. */ $render_query_callback = static function ( $content, $block ) use ( &$enhanced_query_stack, &$dirty_enhanced_queries, &$render_query_callback ) { $has_enhanced_pagination = isset( $block['attrs']['enhancedPagination'] ) && true === $block['attrs']['enhancedPagination'] && isset( $block['attrs']['queryId'] ); if ( ! $has_enhanced_pagination ) { return $content; } if ( isset( $dirty_enhanced_queries[ $block['attrs']['queryId'] ] ) ) { // Disable navigation in the router store config. wp_interactivity_config( 'core/router', array( 'clientNavigationDisabled' => true ) ); $dirty_enhanced_queries[ $block['attrs']['queryId'] ] = null; } array_pop( $enhanced_query_stack ); if ( empty( $enhanced_query_stack ) ) { remove_filter( 'render_block_core/query', $render_query_callback ); $render_query_callback = null; } return $content; }; add_filter( 'render_block_core/query', $render_query_callback, 10, 2 ); } } elseif ( ! empty( $enhanced_query_stack ) && isset( $block_name ) && ( ! $supports_client_navigation ) ) { foreach ( $enhanced_query_stack as $query_id ) { $dirty_enhanced_queries[ $query_id ] = true; } } return $parsed_block; } add_filter( 'render_block_data', 'block_core_query_disable_enhanced_pagination', 10, 1 ); PKt\ m'^!^!latest-posts.phpnu[ $attributes['postsToShow'], 'post_status' => 'publish', 'order' => $attributes['order'], 'orderby' => $attributes['orderBy'], 'ignore_sticky_posts' => true, 'no_found_rows' => true, ); $block_core_latest_posts_excerpt_length = $attributes['excerptLength']; add_filter( 'excerpt_length', 'block_core_latest_posts_get_excerpt_length', 20 ); if ( ! empty( $attributes['categories'] ) ) { $args['category__in'] = array_column( $attributes['categories'], 'id' ); } if ( isset( $attributes['selectedAuthor'] ) ) { $args['author'] = $attributes['selectedAuthor']; } $query = new WP_Query(); $recent_posts = $query->query( $args ); if ( isset( $attributes['displayFeaturedImage'] ) && $attributes['displayFeaturedImage'] ) { update_post_thumbnail_cache( $query ); } $list_items_markup = ''; foreach ( $recent_posts as $post ) { $post_link = esc_url( get_permalink( $post ) ); $title = get_the_title( $post ); if ( ! $title ) { $title = __( '(no title)' ); } $list_items_markup .= '
          • '; if ( $attributes['displayFeaturedImage'] && has_post_thumbnail( $post ) ) { $image_style = ''; if ( isset( $attributes['featuredImageSizeWidth'] ) ) { $image_style .= sprintf( 'max-width:%spx;', $attributes['featuredImageSizeWidth'] ); } if ( isset( $attributes['featuredImageSizeHeight'] ) ) { $image_style .= sprintf( 'max-height:%spx;', $attributes['featuredImageSizeHeight'] ); } $image_classes = 'wp-block-latest-posts__featured-image'; if ( isset( $attributes['featuredImageAlign'] ) ) { $image_classes .= ' align' . $attributes['featuredImageAlign']; } $featured_image = get_the_post_thumbnail( $post, $attributes['featuredImageSizeSlug'], array( 'style' => esc_attr( $image_style ), ) ); if ( $attributes['addLinkToFeaturedImage'] ) { $featured_image = sprintf( '%3$s', esc_url( $post_link ), esc_attr( $title ), $featured_image ); } $list_items_markup .= sprintf( '
            %2$s
            ', esc_attr( $image_classes ), $featured_image ); } $list_items_markup .= sprintf( '%2$s', esc_url( $post_link ), $title ); if ( isset( $attributes['displayAuthor'] ) && $attributes['displayAuthor'] ) { $author_display_name = get_the_author_meta( 'display_name', $post->post_author ); /* translators: byline. %s: author. */ $byline = sprintf( __( 'by %s' ), $author_display_name ); if ( ! empty( $author_display_name ) ) { $list_items_markup .= sprintf( '', $byline ); } } if ( isset( $attributes['displayPostDate'] ) && $attributes['displayPostDate'] ) { $list_items_markup .= sprintf( '', esc_attr( get_the_date( 'c', $post ) ), get_the_date( '', $post ) ); } if ( isset( $attributes['displayPostContent'] ) && $attributes['displayPostContent'] && isset( $attributes['displayPostContentRadio'] ) && 'excerpt' === $attributes['displayPostContentRadio'] ) { $trimmed_excerpt = get_the_excerpt( $post ); /* * Adds a "Read more" link with screen reader text. * […] is the default excerpt ending from wp_trim_excerpt() in Core. */ if ( str_ends_with( $trimmed_excerpt, ' […]' ) ) { /** This filter is documented in wp-includes/formatting.php */ $excerpt_length = (int) apply_filters( 'excerpt_length', $block_core_latest_posts_excerpt_length ); if ( $excerpt_length <= $block_core_latest_posts_excerpt_length ) { $trimmed_excerpt = substr( $trimmed_excerpt, 0, -11 ); $trimmed_excerpt .= sprintf( /* translators: 1: A URL to a post, 2: Hidden accessibility text: Post title */ __( '… Read more: %2$s' ), esc_url( $post_link ), esc_html( $title ) ); } } if ( post_password_required( $post ) ) { $trimmed_excerpt = __( 'This content is password protected.' ); } $list_items_markup .= sprintf( '
            %1$s
            ', $trimmed_excerpt ); } if ( isset( $attributes['displayPostContent'] ) && $attributes['displayPostContent'] && isset( $attributes['displayPostContentRadio'] ) && 'full_post' === $attributes['displayPostContentRadio'] ) { $post_content = html_entity_decode( $post->post_content, ENT_QUOTES, get_option( 'blog_charset' ) ); if ( post_password_required( $post ) ) { $post_content = __( 'This content is password protected.' ); } $list_items_markup .= sprintf( '
            %1$s
            ', wp_kses_post( $post_content ) ); } $list_items_markup .= "
          • \n"; } remove_filter( 'excerpt_length', 'block_core_latest_posts_get_excerpt_length', 20 ); $classes = array( 'wp-block-latest-posts__list' ); if ( isset( $attributes['postLayout'] ) && 'grid' === $attributes['postLayout'] ) { $classes[] = 'is-grid'; } if ( isset( $attributes['columns'] ) && 'grid' === $attributes['postLayout'] ) { $classes[] = 'columns-' . $attributes['columns']; } if ( isset( $attributes['displayPostDate'] ) && $attributes['displayPostDate'] ) { $classes[] = 'has-dates'; } if ( isset( $attributes['displayAuthor'] ) && $attributes['displayAuthor'] ) { $classes[] = 'has-author'; } if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { $classes[] = 'has-link-color'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) ); return sprintf( '
              %2$s
            ', $wrapper_attributes, $list_items_markup ); } /** * Registers the `core/latest-posts` block on server. * * @since 5.0.0 */ function register_block_core_latest_posts() { register_block_type_from_metadata( __DIR__ . '/latest-posts', array( 'render_callback' => 'render_block_core_latest_posts', ) ); } add_action( 'init', 'register_block_core_latest_posts' ); /** * Handles outdated versions of the `core/latest-posts` block by converting * attribute `categories` from a numeric string to an array with key `id`. * * This is done to accommodate the changes introduced in #20781 that sought to * add support for multiple categories to the block. However, given that this * block is dynamic, the usual provisions for block migration are insufficient, * as they only act when a block is loaded in the editor. * * TODO: Remove when and if the bottom client-side deprecation for this block * is removed. * * @since 5.5.0 * * @param array $block A single parsed block object. * * @return array The migrated block object. */ function block_core_latest_posts_migrate_categories( $block ) { if ( 'core/latest-posts' === $block['blockName'] && ! empty( $block['attrs']['categories'] ) && is_string( $block['attrs']['categories'] ) ) { $block['attrs']['categories'] = array( array( 'id' => absint( $block['attrs']['categories'] ) ), ); } return $block; } add_filter( 'render_block_data', 'block_core_latest_posts_migrate_categories' ); PKt\.|comments-title/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/comments-title", "title": "Comments Title", "category": "theme", "ancestor": [ "core/comments" ], "description": "Displays a title with the number of comments.", "textdomain": "default", "usesContext": [ "postId", "postType" ], "attributes": { "textAlign": { "type": "string" }, "showPostTitle": { "type": "boolean", "default": true }, "showCommentsCount": { "type": "boolean", "default": true }, "level": { "type": "number", "default": 2 }, "levelOptions": { "type": "array" } }, "supports": { "anchor": false, "align": true, "html": false, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true }, "color": { "gradients": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "spacing": { "margin": true, "padding": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true, "__experimentalFontFamily": true, "__experimentalFontStyle": true, "__experimentalFontWeight": true } }, "interactivity": { "clientNavigation": true } } } PKt\*z88comments-title/editor.min.cssnu[.wp-block-comments-title.has-background{padding:inherit}PKt\I==comments-title/editor-rtl.cssnu[.wp-block-comments-title.has-background{ padding:inherit; }PKt\*z88!comments-title/editor-rtl.min.cssnu[.wp-block-comments-title.has-background{padding:inherit}PKt\I==comments-title/editor.cssnu[.wp-block-comments-title.has-background{ padding:inherit; }PKt\y>> home-link.phpnu[ array(), 'inline_styles' => '', ); // Text color. $has_named_text_color = array_key_exists( 'textColor', $context ); $has_custom_text_color = isset( $context['style']['color']['text'] ); // If has text color. if ( $has_custom_text_color || $has_named_text_color ) { // Add has-text-color class. $colors['css_classes'][] = 'has-text-color'; } if ( $has_named_text_color ) { // Add the color class. $colors['css_classes'][] = sprintf( 'has-%s-color', $context['textColor'] ); } elseif ( $has_custom_text_color ) { // Add the custom color inline style. $colors['inline_styles'] .= sprintf( 'color: %s;', $context['style']['color']['text'] ); } // Background color. $has_named_background_color = array_key_exists( 'backgroundColor', $context ); $has_custom_background_color = isset( $context['style']['color']['background'] ); // If has background color. if ( $has_custom_background_color || $has_named_background_color ) { // Add has-background class. $colors['css_classes'][] = 'has-background'; } if ( $has_named_background_color ) { // Add the background-color class. $colors['css_classes'][] = sprintf( 'has-%s-background-color', $context['backgroundColor'] ); } elseif ( $has_custom_background_color ) { // Add the custom background-color inline style. $colors['inline_styles'] .= sprintf( 'background-color: %s;', $context['style']['color']['background'] ); } return $colors; } /** * Build an array with CSS classes and inline styles defining the font sizes * which will be applied to the home link markup in the front-end. * * @since 6.0.0 * * @param array $context Home link block context. * @return array Font size CSS classes and inline styles. */ function block_core_home_link_build_css_font_sizes( $context ) { // CSS classes. $font_sizes = array( 'css_classes' => array(), 'inline_styles' => '', ); $has_named_font_size = array_key_exists( 'fontSize', $context ); $has_custom_font_size = isset( $context['style']['typography']['fontSize'] ); if ( $has_named_font_size ) { // Add the font size class. $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] ); } elseif ( $has_custom_font_size ) { // Add the custom font size inline style. $font_sizes['inline_styles'] = sprintf( 'font-size: %s;', $context['style']['typography']['fontSize'] ); } return $font_sizes; } /** * Builds an array with classes and style for the li wrapper * * @since 6.0.0 * * @param array $context Home link block context. * @return string The li wrapper attributes. */ function block_core_home_link_build_li_wrapper_attributes( $context ) { $colors = block_core_home_link_build_css_colors( $context ); $font_sizes = block_core_home_link_build_css_font_sizes( $context ); $classes = array_merge( $colors['css_classes'], $font_sizes['css_classes'] ); $style_attribute = ( $colors['inline_styles'] . $font_sizes['inline_styles'] ); $classes[] = 'wp-block-navigation-item'; if ( is_front_page() ) { $classes[] = 'current-menu-item'; } elseif ( is_home() && ( (int) get_option( 'page_for_posts' ) !== get_queried_object_id() ) ) { // Edge case where the Reading settings has a posts page set but not a static homepage. $classes[] = 'current-menu-item'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ), 'style' => $style_attribute, ) ); return $wrapper_attributes; } /** * Renders the `core/home-link` block. * * @since 6.0.0 * * @param array $attributes The block attributes. * @param string $content The saved content. * @param WP_Block $block The parsed block. * * @return string Returns the post content with the home url added. */ function render_block_core_home_link( $attributes, $content, $block ) { if ( empty( $attributes['label'] ) ) { $attributes['label'] = __( 'Home' ); } $aria_current = ''; if ( is_front_page() ) { $aria_current = ' aria-current="page"'; } elseif ( is_home() && ( (int) get_option( 'page_for_posts' ) !== get_queried_object_id() ) ) { // Edge case where the Reading settings has a posts page set but not a static homepage. $aria_current = ' aria-current="page"'; } return sprintf( '
          • %4$s
          • ', block_core_home_link_build_li_wrapper_attributes( $block->context ), esc_url( home_url() ), $aria_current, wp_kses_post( $attributes['label'] ) ); } /** * Register the home block * * @since 6.0.0 * * @uses render_block_core_home_link() * @throws WP_Error An WP_Error exception parsing the block definition. */ function register_block_core_home_link() { register_block_type_from_metadata( __DIR__ . '/home-link', array( 'render_callback' => 'render_block_core_home_link', ) ); } add_action( 'init', 'register_block_core_home_link' ); PKt\h8page-list/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/page-list", "title": "Page List", "category": "widgets", "allowedBlocks": [ "core/page-list-item" ], "description": "Display a list of all pages.", "keywords": [ "menu", "navigation" ], "textdomain": "default", "attributes": { "parentPageID": { "type": "integer", "default": 0 }, "isNested": { "type": "boolean", "default": false } }, "usesContext": [ "textColor", "customTextColor", "backgroundColor", "customBackgroundColor", "overlayTextColor", "customOverlayTextColor", "overlayBackgroundColor", "customOverlayBackgroundColor", "fontSize", "customFontSize", "showSubmenuIcon", "style", "openSubmenusOnClick" ], "supports": { "reusable": false, "html": false, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "color": { "text": true, "background": true, "link": true, "gradients": true, "__experimentalDefaultControls": { "background": true, "text": true, "link": true } }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true }, "spacing": { "padding": true, "margin": true, "__experimentalDefaultControls": { "padding": false, "margin": false } } }, "editorStyle": "wp-block-page-list-editor", "style": "wp-block-page-list" } PKt\ԏpage-list/editor.min.cssnu[.wp-block-navigation .wp-block-page-list,.wp-block-navigation .wp-block-page-list>div{background-color:inherit}.wp-block-navigation.items-justified-space-between .wp-block-page-list,.wp-block-navigation.items-justified-space-between .wp-block-page-list>div{display:contents;flex:1}.wp-block-navigation.items-justified-space-between.has-child-selected .wp-block-page-list,.wp-block-navigation.items-justified-space-between.has-child-selected .wp-block-page-list>div,.wp-block-navigation.items-justified-space-between.is-selected .wp-block-page-list,.wp-block-navigation.items-justified-space-between.is-selected .wp-block-page-list>div{flex:inherit}.wp-block-navigation .wp-block-navigation__submenu-container>.wp-block-page-list{display:block}.wp-block-pages-list__item__link{pointer-events:none}@media (min-width:600px){.wp-block-page-list-modal{max-width:480px}}.wp-block-page-list-modal-buttons{display:flex;gap:12px;justify-content:flex-end}.wp-block-page-list .open-on-click:focus-within>.wp-block-navigation__submenu-container{height:auto;min-width:200px;opacity:1;visibility:visible;width:auto}.wp-block-page-list__loading-indicator-container{padding:8px 12px}PKt\犔page-list/style.min.cssnu[.wp-block-navigation .wp-block-page-list{align-items:var(--navigation-layout-align,initial);background-color:inherit;display:flex;flex-direction:var(--navigation-layout-direction,initial);flex-wrap:var(--navigation-layout-wrap,wrap);justify-content:var(--navigation-layout-justify,initial)}.wp-block-navigation .wp-block-navigation-item{background-color:inherit}.wp-block-page-list{box-sizing:border-box}PKt\ipage-list/style-rtl.cssnu[.wp-block-navigation .wp-block-page-list{ align-items:var(--navigation-layout-align, initial); background-color:inherit; display:flex; flex-direction:var(--navigation-layout-direction, initial); flex-wrap:var(--navigation-layout-wrap, wrap); justify-content:var(--navigation-layout-justify, initial); } .wp-block-navigation .wp-block-navigation-item{ background-color:inherit; } .wp-block-page-list{ box-sizing:border-box; }PKt\]page-list/editor-rtl.cssnu[.wp-block-navigation .wp-block-page-list,.wp-block-navigation .wp-block-page-list>div{ background-color:inherit; } .wp-block-navigation.items-justified-space-between .wp-block-page-list,.wp-block-navigation.items-justified-space-between .wp-block-page-list>div{ display:contents; flex:1; } .wp-block-navigation.items-justified-space-between.has-child-selected .wp-block-page-list,.wp-block-navigation.items-justified-space-between.has-child-selected .wp-block-page-list>div,.wp-block-navigation.items-justified-space-between.is-selected .wp-block-page-list,.wp-block-navigation.items-justified-space-between.is-selected .wp-block-page-list>div{ flex:inherit; } .wp-block-navigation .wp-block-navigation__submenu-container>.wp-block-page-list{ display:block; } .wp-block-pages-list__item__link{ pointer-events:none; } @media (min-width:600px){ .wp-block-page-list-modal{ max-width:480px; } } .wp-block-page-list-modal-buttons{ display:flex; gap:12px; justify-content:flex-end; } .wp-block-page-list .open-on-click:focus-within>.wp-block-navigation__submenu-container{ height:auto; min-width:200px; opacity:1; visibility:visible; width:auto; } .wp-block-page-list__loading-indicator-container{ padding:8px 12px; }PKt\ipage-list/style.cssnu[.wp-block-navigation .wp-block-page-list{ align-items:var(--navigation-layout-align, initial); background-color:inherit; display:flex; flex-direction:var(--navigation-layout-direction, initial); flex-wrap:var(--navigation-layout-wrap, wrap); justify-content:var(--navigation-layout-justify, initial); } .wp-block-navigation .wp-block-navigation-item{ background-color:inherit; } .wp-block-page-list{ box-sizing:border-box; }PKt\犔page-list/style-rtl.min.cssnu[.wp-block-navigation .wp-block-page-list{align-items:var(--navigation-layout-align,initial);background-color:inherit;display:flex;flex-direction:var(--navigation-layout-direction,initial);flex-wrap:var(--navigation-layout-wrap,wrap);justify-content:var(--navigation-layout-justify,initial)}.wp-block-navigation .wp-block-navigation-item{background-color:inherit}.wp-block-page-list{box-sizing:border-box}PKt\ԏpage-list/editor-rtl.min.cssnu[.wp-block-navigation .wp-block-page-list,.wp-block-navigation .wp-block-page-list>div{background-color:inherit}.wp-block-navigation.items-justified-space-between .wp-block-page-list,.wp-block-navigation.items-justified-space-between .wp-block-page-list>div{display:contents;flex:1}.wp-block-navigation.items-justified-space-between.has-child-selected .wp-block-page-list,.wp-block-navigation.items-justified-space-between.has-child-selected .wp-block-page-list>div,.wp-block-navigation.items-justified-space-between.is-selected .wp-block-page-list,.wp-block-navigation.items-justified-space-between.is-selected .wp-block-page-list>div{flex:inherit}.wp-block-navigation .wp-block-navigation__submenu-container>.wp-block-page-list{display:block}.wp-block-pages-list__item__link{pointer-events:none}@media (min-width:600px){.wp-block-page-list-modal{max-width:480px}}.wp-block-page-list-modal-buttons{display:flex;gap:12px;justify-content:flex-end}.wp-block-page-list .open-on-click:focus-within>.wp-block-navigation__submenu-container{height:auto;min-width:200px;opacity:1;visibility:visible;width:auto}.wp-block-page-list__loading-indicator-container{padding:8px 12px}PKt\]page-list/editor.cssnu[.wp-block-navigation .wp-block-page-list,.wp-block-navigation .wp-block-page-list>div{ background-color:inherit; } .wp-block-navigation.items-justified-space-between .wp-block-page-list,.wp-block-navigation.items-justified-space-between .wp-block-page-list>div{ display:contents; flex:1; } .wp-block-navigation.items-justified-space-between.has-child-selected .wp-block-page-list,.wp-block-navigation.items-justified-space-between.has-child-selected .wp-block-page-list>div,.wp-block-navigation.items-justified-space-between.is-selected .wp-block-page-list,.wp-block-navigation.items-justified-space-between.is-selected .wp-block-page-list>div{ flex:inherit; } .wp-block-navigation .wp-block-navigation__submenu-container>.wp-block-page-list{ display:block; } .wp-block-pages-list__item__link{ pointer-events:none; } @media (min-width:600px){ .wp-block-page-list-modal{ max-width:480px; } } .wp-block-page-list-modal-buttons{ display:flex; gap:12px; justify-content:flex-end; } .wp-block-page-list .open-on-click:focus-within>.wp-block-navigation__submenu-container{ height:auto; min-width:200px; opacity:1; visibility:visible; width:auto; } .wp-block-page-list__loading-indicator-container{ padding:8px 12px; }PKt\5fflatest-comments/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/latest-comments", "title": "Latest Comments", "category": "widgets", "description": "Display a list of your most recent comments.", "keywords": [ "recent comments" ], "textdomain": "default", "attributes": { "commentsToShow": { "type": "number", "default": 5, "minimum": 1, "maximum": 100 }, "displayAvatar": { "type": "boolean", "default": true }, "displayDate": { "type": "boolean", "default": true }, "displayExcerpt": { "type": "boolean", "default": true } }, "supports": { "align": true, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true, "link": true } }, "html": false, "spacing": { "margin": true, "padding": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-latest-comments-editor", "style": "wp-block-latest-comments" } PKt\/latest-comments/style.min.cssnu[ol.wp-block-latest-comments{box-sizing:border-box;margin-left:0}:where(.wp-block-latest-comments:not([style*=line-height] .wp-block-latest-comments__comment)){line-height:1.1}:where(.wp-block-latest-comments:not([style*=line-height] .wp-block-latest-comments__comment-excerpt p)){line-height:1.8}.has-dates :where(.wp-block-latest-comments:not([style*=line-height])),.has-excerpts :where(.wp-block-latest-comments:not([style*=line-height])){line-height:1.5}.wp-block-latest-comments .wp-block-latest-comments{padding-left:0}.wp-block-latest-comments__comment{list-style:none;margin-bottom:1em}.has-avatars .wp-block-latest-comments__comment{list-style:none;min-height:2.25em}.has-avatars .wp-block-latest-comments__comment .wp-block-latest-comments__comment-excerpt,.has-avatars .wp-block-latest-comments__comment .wp-block-latest-comments__comment-meta{margin-left:3.25em}.wp-block-latest-comments__comment-excerpt p{font-size:.875em;margin:.36em 0 1.4em}.wp-block-latest-comments__comment-date{display:block;font-size:.75em}.wp-block-latest-comments .avatar,.wp-block-latest-comments__comment-avatar{border-radius:1.5em;display:block;float:left;height:2.5em;margin-right:.75em;width:2.5em}.wp-block-latest-comments[class*=-font-size] a,.wp-block-latest-comments[style*=font-size] a{font-size:inherit}PKt\2;mlatest-comments/style-rtl.cssnu[ol.wp-block-latest-comments{ box-sizing:border-box; margin-right:0; } :where(.wp-block-latest-comments:not([style*=line-height] .wp-block-latest-comments__comment)){ line-height:1.1; } :where(.wp-block-latest-comments:not([style*=line-height] .wp-block-latest-comments__comment-excerpt p)){ line-height:1.8; } .has-dates :where(.wp-block-latest-comments:not([style*=line-height])),.has-excerpts :where(.wp-block-latest-comments:not([style*=line-height])){ line-height:1.5; } .wp-block-latest-comments .wp-block-latest-comments{ padding-right:0; } .wp-block-latest-comments__comment{ list-style:none; margin-bottom:1em; } .has-avatars .wp-block-latest-comments__comment{ list-style:none; min-height:2.25em; } .has-avatars .wp-block-latest-comments__comment .wp-block-latest-comments__comment-excerpt,.has-avatars .wp-block-latest-comments__comment .wp-block-latest-comments__comment-meta{ margin-right:3.25em; } .wp-block-latest-comments__comment-excerpt p{ font-size:.875em; margin:.36em 0 1.4em; } .wp-block-latest-comments__comment-date{ display:block; font-size:.75em; } .wp-block-latest-comments .avatar,.wp-block-latest-comments__comment-avatar{ border-radius:1.5em; display:block; float:right; height:2.5em; margin-left:.75em; width:2.5em; } .wp-block-latest-comments[class*=-font-size] a,.wp-block-latest-comments[style*=font-size] a{ font-size:inherit; }PKt\Shlatest-comments/style.cssnu[ol.wp-block-latest-comments{ box-sizing:border-box; margin-left:0; } :where(.wp-block-latest-comments:not([style*=line-height] .wp-block-latest-comments__comment)){ line-height:1.1; } :where(.wp-block-latest-comments:not([style*=line-height] .wp-block-latest-comments__comment-excerpt p)){ line-height:1.8; } .has-dates :where(.wp-block-latest-comments:not([style*=line-height])),.has-excerpts :where(.wp-block-latest-comments:not([style*=line-height])){ line-height:1.5; } .wp-block-latest-comments .wp-block-latest-comments{ padding-left:0; } .wp-block-latest-comments__comment{ list-style:none; margin-bottom:1em; } .has-avatars .wp-block-latest-comments__comment{ list-style:none; min-height:2.25em; } .has-avatars .wp-block-latest-comments__comment .wp-block-latest-comments__comment-excerpt,.has-avatars .wp-block-latest-comments__comment .wp-block-latest-comments__comment-meta{ margin-left:3.25em; } .wp-block-latest-comments__comment-excerpt p{ font-size:.875em; margin:.36em 0 1.4em; } .wp-block-latest-comments__comment-date{ display:block; font-size:.75em; } .wp-block-latest-comments .avatar,.wp-block-latest-comments__comment-avatar{ border-radius:1.5em; display:block; float:left; height:2.5em; margin-right:.75em; width:2.5em; } .wp-block-latest-comments[class*=-font-size] a,.wp-block-latest-comments[style*=font-size] a{ font-size:inherit; }PKt\!latest-comments/style-rtl.min.cssnu[ol.wp-block-latest-comments{box-sizing:border-box;margin-right:0}:where(.wp-block-latest-comments:not([style*=line-height] .wp-block-latest-comments__comment)){line-height:1.1}:where(.wp-block-latest-comments:not([style*=line-height] .wp-block-latest-comments__comment-excerpt p)){line-height:1.8}.has-dates :where(.wp-block-latest-comments:not([style*=line-height])),.has-excerpts :where(.wp-block-latest-comments:not([style*=line-height])){line-height:1.5}.wp-block-latest-comments .wp-block-latest-comments{padding-right:0}.wp-block-latest-comments__comment{list-style:none;margin-bottom:1em}.has-avatars .wp-block-latest-comments__comment{list-style:none;min-height:2.25em}.has-avatars .wp-block-latest-comments__comment .wp-block-latest-comments__comment-excerpt,.has-avatars .wp-block-latest-comments__comment .wp-block-latest-comments__comment-meta{margin-right:3.25em}.wp-block-latest-comments__comment-excerpt p{font-size:.875em;margin:.36em 0 1.4em}.wp-block-latest-comments__comment-date{display:block;font-size:.75em}.wp-block-latest-comments .avatar,.wp-block-latest-comments__comment-avatar{border-radius:1.5em;display:block;float:right;height:2.5em;margin-left:.75em;width:2.5em}.wp-block-latest-comments[class*=-font-size] a,.wp-block-latest-comments[style*=font-size] a{font-size:inherit}PKt\U==site-title.phpnu[%4$s', esc_url( home_url() ), esc_attr( $link_target ), $aria_current, esc_html( $site_title ) ); } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( $classes ) ) ); return sprintf( '<%1$s %2$s>%3$s', $tag_name, $wrapper_attributes, // already pre-escaped if it is a link. $attributes['isLink'] ? $site_title : esc_html( $site_title ) ); } /** * Registers the `core/site-title` block on the server. * * @since 5.8.0 */ function register_block_core_site_title() { register_block_type_from_metadata( __DIR__ . '/site-title', array( 'render_callback' => 'render_block_core_site_title', ) ); } add_action( 'init', 'register_block_core_site_title' ); PKt\*query-title/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/query-title", "title": "Query Title", "category": "theme", "description": "Display the query title.", "textdomain": "default", "attributes": { "type": { "type": "string" }, "textAlign": { "type": "string" }, "level": { "type": "number", "default": 1 }, "levelOptions": { "type": "array" }, "showPrefix": { "type": "boolean", "default": true }, "showSearchTerm": { "type": "boolean", "default": true } }, "example": { "attributes": { "type": "search" } }, "supports": { "align": [ "wide", "full" ], "html": false, "color": { "gradients": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "spacing": { "margin": true, "padding": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontStyle": true, "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "style": "wp-block-query-title" } PKt\2/u,,query-title/style.min.cssnu[.wp-block-query-title{box-sizing:border-box}PKt\K %11query-title/style-rtl.cssnu[.wp-block-query-title{ box-sizing:border-box; }PKt\K %11query-title/style.cssnu[.wp-block-query-title{ box-sizing:border-box; }PKt\2/u,,query-title/style-rtl.min.cssnu[.wp-block-query-title{box-sizing:border-box}PKt\& dpost-author/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/post-author", "title": "Author", "category": "theme", "description": "Display post author details such as name, avatar, and bio.", "textdomain": "default", "attributes": { "textAlign": { "type": "string" }, "avatarSize": { "type": "number", "default": 48 }, "showAvatar": { "type": "boolean", "default": true }, "showBio": { "type": "boolean" }, "byline": { "type": "string" }, "isLink": { "type": "boolean", "default": false, "role": "content" }, "linkTarget": { "type": "string", "default": "_self", "role": "content" } }, "usesContext": [ "postType", "postId", "queryId" ], "supports": { "html": false, "spacing": { "margin": true, "padding": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "color": { "gradients": true, "link": true, "__experimentalDuotone": ".wp-block-post-author__avatar img", "__experimentalDefaultControls": { "background": true, "text": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "editorStyle": "wp-block-post-author-editor", "style": "wp-block-post-author" } PKt\z]post-author/editor.min.cssnu[.wp-block-post-author__inspector-settings .components-base-control,.wp-block-post-author__inspector-settings .components-base-control:last-child{margin-bottom:0}PKt\DJaffpost-author/style.min.cssnu[.wp-block-post-author{box-sizing:border-box;display:flex;flex-wrap:wrap}.wp-block-post-author__byline{font-size:.5em;margin-bottom:0;margin-top:0;width:100%}.wp-block-post-author__avatar{margin-right:1em}.wp-block-post-author__bio{font-size:.7em;margin-bottom:.7em}.wp-block-post-author__content{flex-basis:0;flex-grow:1}.wp-block-post-author__name{margin:0}PKt\ypost-author/style-rtl.cssnu[.wp-block-post-author{ box-sizing:border-box; display:flex; flex-wrap:wrap; } .wp-block-post-author__byline{ font-size:.5em; margin-bottom:0; margin-top:0; width:100%; } .wp-block-post-author__avatar{ margin-left:1em; } .wp-block-post-author__bio{ font-size:.7em; margin-bottom:.7em; } .wp-block-post-author__content{ flex-basis:0; flex-grow:1; } .wp-block-post-author__name{ margin:0; }PKt\чskpost-author/editor-rtl.cssnu[.wp-block-post-author__inspector-settings .components-base-control,.wp-block-post-author__inspector-settings .components-base-control:last-child{ margin-bottom:0; }PKt\g72post-author/style.cssnu[.wp-block-post-author{ box-sizing:border-box; display:flex; flex-wrap:wrap; } .wp-block-post-author__byline{ font-size:.5em; margin-bottom:0; margin-top:0; width:100%; } .wp-block-post-author__avatar{ margin-right:1em; } .wp-block-post-author__bio{ font-size:.7em; margin-bottom:.7em; } .wp-block-post-author__content{ flex-basis:0; flex-grow:1; } .wp-block-post-author__name{ margin:0; }PKt\6Teepost-author/style-rtl.min.cssnu[.wp-block-post-author{box-sizing:border-box;display:flex;flex-wrap:wrap}.wp-block-post-author__byline{font-size:.5em;margin-bottom:0;margin-top:0;width:100%}.wp-block-post-author__avatar{margin-left:1em}.wp-block-post-author__bio{font-size:.7em;margin-bottom:.7em}.wp-block-post-author__content{flex-basis:0;flex-grow:1}.wp-block-post-author__name{margin:0}PKt\z]post-author/editor-rtl.min.cssnu[.wp-block-post-author__inspector-settings .components-base-control,.wp-block-post-author__inspector-settings .components-base-control:last-child{margin-bottom:0}PKt\чskpost-author/editor.cssnu[.wp-block-post-author__inspector-settings .components-base-control,.wp-block-post-author__inspector-settings .components-base-control:last-child{ margin-bottom:0; }PKt\Ytemplate-part/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/template-part", "title": "Template Part", "category": "theme", "description": "Edit the different global regions of your site, like the header, footer, sidebar, or create your own.", "textdomain": "default", "attributes": { "slug": { "type": "string" }, "theme": { "type": "string" }, "tagName": { "type": "string" }, "area": { "type": "string" } }, "supports": { "align": true, "html": false, "reusable": false, "renaming": false, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-template-part-editor" } PKt\PPtemplate-part/editor.min.cssnu[.block-editor-template-part__selection-modal{z-index:1000001}.block-editor-template-part__selection-modal .block-editor-block-patterns-list{column-count:2;column-gap:24px}@media (min-width:1280px){.block-editor-template-part__selection-modal .block-editor-block-patterns-list{column-count:3}}.block-editor-template-part__selection-modal .block-editor-block-patterns-list .block-editor-block-patterns-list__list-item{break-inside:avoid-column}.block-library-template-part__selection-search{background:#fff;padding:16px 0;position:sticky;top:0;z-index:2}.block-editor-block-list__block:not(.remove-outline).is-reusable.block-editor-block-list__block:not([contenteditable]):focus:after,.block-editor-block-list__block:not(.remove-outline).is-reusable.is-highlighted:after,.block-editor-block-list__block:not(.remove-outline).is-reusable.is-selected:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.block-editor-block-list__block:not([contenteditable]):focus:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.is-highlighted:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.is-selected:after{outline-color:var(--wp-block-synced-color)}.is-outline-mode .block-editor-block-list__block:not(.remove-outline).wp-block-template-part.has-editable-outline:after{border:none}PKt\%iitemplate-part/theme.min.cssnu[:root :where(.wp-block-template-part.has-background){margin-bottom:0;margin-top:0;padding:1.25em 2.375em}PKt\')tttemplate-part/theme.cssnu[:root :where(.wp-block-template-part.has-background){ margin-bottom:0; margin-top:0; padding:1.25em 2.375em; }PKt\+stemplate-part/editor-rtl.cssnu[.block-editor-template-part__selection-modal{ z-index:1000001; } .block-editor-template-part__selection-modal .block-editor-block-patterns-list{ column-count:2; column-gap:24px; } @media (min-width:1280px){ .block-editor-template-part__selection-modal .block-editor-block-patterns-list{ column-count:3; } } .block-editor-template-part__selection-modal .block-editor-block-patterns-list .block-editor-block-patterns-list__list-item{ break-inside:avoid-column; } .block-library-template-part__selection-search{ background:#fff; padding:16px 0; position:sticky; top:0; z-index:2; } .block-editor-block-list__block:not(.remove-outline).is-reusable.block-editor-block-list__block:not([contenteditable]):focus:after,.block-editor-block-list__block:not(.remove-outline).is-reusable.is-highlighted:after,.block-editor-block-list__block:not(.remove-outline).is-reusable.is-selected:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.block-editor-block-list__block:not([contenteditable]):focus:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.is-highlighted:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.is-selected:after{ outline-color:var(--wp-block-synced-color); } .is-outline-mode .block-editor-block-list__block:not(.remove-outline).wp-block-template-part.has-editable-outline:after{ border:none; }PKt\')tttemplate-part/theme-rtl.cssnu[:root :where(.wp-block-template-part.has-background){ margin-bottom:0; margin-top:0; padding:1.25em 2.375em; }PKt\%iitemplate-part/theme-rtl.min.cssnu[:root :where(.wp-block-template-part.has-background){margin-bottom:0;margin-top:0;padding:1.25em 2.375em}PKt\PP template-part/editor-rtl.min.cssnu[.block-editor-template-part__selection-modal{z-index:1000001}.block-editor-template-part__selection-modal .block-editor-block-patterns-list{column-count:2;column-gap:24px}@media (min-width:1280px){.block-editor-template-part__selection-modal .block-editor-block-patterns-list{column-count:3}}.block-editor-template-part__selection-modal .block-editor-block-patterns-list .block-editor-block-patterns-list__list-item{break-inside:avoid-column}.block-library-template-part__selection-search{background:#fff;padding:16px 0;position:sticky;top:0;z-index:2}.block-editor-block-list__block:not(.remove-outline).is-reusable.block-editor-block-list__block:not([contenteditable]):focus:after,.block-editor-block-list__block:not(.remove-outline).is-reusable.is-highlighted:after,.block-editor-block-list__block:not(.remove-outline).is-reusable.is-selected:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.block-editor-block-list__block:not([contenteditable]):focus:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.is-highlighted:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.is-selected:after{outline-color:var(--wp-block-synced-color)}.is-outline-mode .block-editor-block-list__block:not(.remove-outline).wp-block-template-part.has-editable-outline:after{border:none}PKt\+stemplate-part/editor.cssnu[.block-editor-template-part__selection-modal{ z-index:1000001; } .block-editor-template-part__selection-modal .block-editor-block-patterns-list{ column-count:2; column-gap:24px; } @media (min-width:1280px){ .block-editor-template-part__selection-modal .block-editor-block-patterns-list{ column-count:3; } } .block-editor-template-part__selection-modal .block-editor-block-patterns-list .block-editor-block-patterns-list__list-item{ break-inside:avoid-column; } .block-library-template-part__selection-search{ background:#fff; padding:16px 0; position:sticky; top:0; z-index:2; } .block-editor-block-list__block:not(.remove-outline).is-reusable.block-editor-block-list__block:not([contenteditable]):focus:after,.block-editor-block-list__block:not(.remove-outline).is-reusable.is-highlighted:after,.block-editor-block-list__block:not(.remove-outline).is-reusable.is-selected:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.block-editor-block-list__block:not([contenteditable]):focus:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.is-highlighted:after,.block-editor-block-list__block:not(.remove-outline).wp-block-template-part.is-selected:after{ outline-color:var(--wp-block-synced-color); } .is-outline-mode .block-editor-block-list__block:not(.remove-outline).wp-block-template-part.has-editable-outline:after{ border:none; }PKt\7\read-more/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/read-more", "title": "Read More", "category": "theme", "description": "Displays the link of a post, page, or any other content-type.", "textdomain": "default", "attributes": { "content": { "type": "string" }, "linkTarget": { "type": "string", "default": "_self" } }, "usesContext": [ "postId" ], "supports": { "html": false, "color": { "gradients": true, "text": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalLetterSpacing": true, "__experimentalTextDecoration": true, "__experimentalDefaultControls": { "fontSize": true, "textDecoration": true } }, "spacing": { "margin": [ "top", "bottom" ], "padding": true, "__experimentalDefaultControls": { "padding": true } }, "__experimentalBorder": { "color": true, "radius": true, "width": true, "__experimentalDefaultControls": { "width": true } }, "interactivity": { "clientNavigation": true } }, "style": "wp-block-read-more" } PKt\2Hread-more/style.min.cssnu[.wp-block-read-more{display:block;width:fit-content}.wp-block-read-more:where(:not([style*=text-decoration])){text-decoration:none}.wp-block-read-more:where(:not([style*=text-decoration])):active,.wp-block-read-more:where(:not([style*=text-decoration])):focus{text-decoration:none}PKt\+--read-more/style-rtl.cssnu[.wp-block-read-more{ display:block; width:fit-content; } .wp-block-read-more:where(:not([style*=text-decoration])){ text-decoration:none; } .wp-block-read-more:where(:not([style*=text-decoration])):active,.wp-block-read-more:where(:not([style*=text-decoration])):focus{ text-decoration:none; }PKt\+--read-more/style.cssnu[.wp-block-read-more{ display:block; width:fit-content; } .wp-block-read-more:where(:not([style*=text-decoration])){ text-decoration:none; } .wp-block-read-more:where(:not([style*=text-decoration])):active,.wp-block-read-more:where(:not([style*=text-decoration])):focus{ text-decoration:none; }PKt\2Hread-more/style-rtl.min.cssnu[.wp-block-read-more{display:block;width:fit-content}.wp-block-read-more:where(:not([style*=text-decoration])){text-decoration:none}.wp-block-read-more:where(:not([style*=text-decoration])):active,.wp-block-read-more:where(:not([style*=text-decoration])):focus{text-decoration:none}PKt\OVƮfile.phpnu[next_tag(); $processor->set_attribute( 'data-wp-interactive', 'core/file' ); $processor->next_tag( 'object' ); $processor->set_attribute( 'data-wp-bind--hidden', '!state.hasPdfPreview' ); $processor->set_attribute( 'hidden', true ); $filename = $processor->get_attribute( 'aria-label' ); $has_filename = ! empty( $filename ) && 'PDF embed' !== $filename; $label = $has_filename ? sprintf( /* translators: %s: filename. */ __( 'Embed of %s.' ), $filename ) : __( 'PDF embed' ); // Update object's aria-label attribute if present in block HTML. // Match an aria-label attribute from an object tag. $processor->set_attribute( 'aria-label', $label ); return $processor->get_updated_html(); } return $content; } /** * Registers the `core/file` block on server. * * @since 5.8.0 */ function register_block_core_file() { register_block_type_from_metadata( __DIR__ . '/file', array( 'render_callback' => 'render_block_core_file', ) ); } add_action( 'init', 'register_block_core_file' ); PKt\Dquery-pagination-previous.phpnu[context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination']; $max_page = isset( $block->context['query']['pages'] ) ? (int) $block->context['query']['pages'] : 0; $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; $wrapper_attributes = get_block_wrapper_attributes(); $show_label = isset( $block->context['showLabel'] ) ? (bool) $block->context['showLabel'] : true; $default_label = __( 'Previous Page' ); $label_text = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? esc_html( $attributes['label'] ) : $default_label; $label = $show_label ? $label_text : ''; $pagination_arrow = get_query_pagination_arrow( $block, false ); if ( ! $label ) { $wrapper_attributes .= ' aria-label="' . $label_text . '"'; } if ( $pagination_arrow ) { $label = $pagination_arrow . $label; } $content = ''; // Check if the pagination is for Query that inherits the global context // and handle appropriately. if ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ) { $filter_link_attributes = static function () use ( $wrapper_attributes ) { return $wrapper_attributes; }; add_filter( 'previous_posts_link_attributes', $filter_link_attributes ); $content = get_previous_posts_link( $label ); remove_filter( 'previous_posts_link_attributes', $filter_link_attributes ); } else { $block_query = new WP_Query( build_query_vars_from_query_block( $block, $page ) ); $block_max_pages = $block_query->max_num_pages; $total = ! $max_page || $max_page > $block_max_pages ? $block_max_pages : $max_page; wp_reset_postdata(); if ( 1 < $page && $page <= $total ) { $content = sprintf( '%3$s', esc_url( add_query_arg( $page_key, $page - 1 ) ), $wrapper_attributes, $label ); } } if ( $enhanced_pagination && isset( $content ) ) { $p = new WP_HTML_Tag_Processor( $content ); if ( $p->next_tag( array( 'tag_name' => 'a', 'class_name' => 'wp-block-query-pagination-previous', ) ) ) { $p->set_attribute( 'data-wp-key', 'query-pagination-previous' ); $p->set_attribute( 'data-wp-on--click', 'core/query::actions.navigate' ); $p->set_attribute( 'data-wp-on-async--mouseenter', 'core/query::actions.prefetch' ); $p->set_attribute( 'data-wp-watch', 'core/query::callbacks.prefetch' ); $content = $p->get_updated_html(); } } return $content; } /** * Registers the `core/query-pagination-previous` block on the server. * * @since 5.8.0 */ function register_block_core_query_pagination_previous() { register_block_type_from_metadata( __DIR__ . '/query-pagination-previous', array( 'render_callback' => 'render_block_core_query_pagination_previous', ) ); } add_action( 'init', 'register_block_core_query_pagination_previous' ); PKt\0xsite-tagline/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/site-tagline", "title": "Site Tagline", "category": "theme", "description": "Describe in a few words what the site is about. The tagline can be used in search results or when sharing on social networks even if it’s not displayed in the theme design.", "keywords": [ "description" ], "textdomain": "default", "attributes": { "textAlign": { "type": "string" }, "level": { "type": "number", "default": 0 }, "levelOptions": { "type": "array", "default": [ 0, 1, 2, 3, 4, 5, 6 ] } }, "example": { "viewportWidth": 350, "attributes": { "textAlign": "center" } }, "supports": { "align": [ "wide", "full" ], "html": false, "color": { "gradients": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalFontStyle": true, "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, "__experimentalWritingMode": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true } }, "editorStyle": "wp-block-site-tagline-editor", "style": "wp-block-site-tagline" } PKt\ß~qDDsite-tagline/editor.min.cssnu[.wp-block-site-tagline__placeholder{border:1px dashed;padding:1em 0}PKt\v--site-tagline/style.min.cssnu[.wp-block-site-tagline{box-sizing:border-box}PKt\ S22site-tagline/style-rtl.cssnu[.wp-block-site-tagline{ box-sizing:border-box; }PKt\LLsite-tagline/editor-rtl.cssnu[.wp-block-site-tagline__placeholder{ border:1px dashed; padding:1em 0; }PKt\ S22site-tagline/style.cssnu[.wp-block-site-tagline{ box-sizing:border-box; }PKt\v--site-tagline/style-rtl.min.cssnu[.wp-block-site-tagline{box-sizing:border-box}PKt\ß~qDDsite-tagline/editor-rtl.min.cssnu[.wp-block-site-tagline__placeholder{border:1px dashed;padding:1em 0}PKt\LLsite-tagline/editor.cssnu[.wp-block-site-tagline__placeholder{ border:1px dashed; padding:1em 0; }PKt\. post-author.phpnu[context['postId'] ) ) { $author_id = get_query_var( 'author' ); } else { $author_id = get_post_field( 'post_author', $block->context['postId'] ); } if ( empty( $author_id ) ) { return ''; } if ( isset( $block->context['postType'] ) && ! post_type_supports( $block->context['postType'], 'author' ) ) { return ''; } $avatar = ! empty( $attributes['avatarSize'] ) ? get_avatar( $author_id, $attributes['avatarSize'] ) : null; $link = get_author_posts_url( $author_id ); $author_name = get_the_author_meta( 'display_name', $author_id ); if ( ! empty( $attributes['isLink'] && ! empty( $attributes['linkTarget'] ) ) ) { $author_name = sprintf( '%3$s', esc_url( $link ), esc_attr( $attributes['linkTarget'] ), $author_name ); } $byline = ! empty( $attributes['byline'] ) ? $attributes['byline'] : false; $classes = array(); if ( isset( $attributes['itemsJustification'] ) ) { $classes[] = 'items-justified-' . $attributes['itemsJustification']; } if ( isset( $attributes['textAlign'] ) ) { $classes[] = 'has-text-align-' . $attributes['textAlign']; } if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { $classes[] = 'has-link-color'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) ); return sprintf( '
            ', $wrapper_attributes ) . ( ! empty( $attributes['showAvatar'] ) ? '' : '' ) . '' . '
            '; } /** * Registers the `core/post-author` block on the server. * * @since 5.9.0 */ function register_block_core_post_author() { register_block_type_from_metadata( __DIR__ . '/post-author', array( 'render_callback' => 'render_block_core_post_author', ) ); } add_action( 'init', 'register_block_core_post_author' ); PKt\,archives/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/archives", "title": "Archives", "category": "widgets", "description": "Display a date archive of your posts.", "textdomain": "default", "attributes": { "displayAsDropdown": { "type": "boolean", "default": false }, "showLabel": { "type": "boolean", "default": true }, "showPostCounts": { "type": "boolean", "default": false }, "type": { "type": "string", "default": "monthly" } }, "supports": { "align": true, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true }, "html": false, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true, "link": true } }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-archives-editor" } PKt\p``archives/editor.min.cssnu[ul.wp-block-archives{padding-left:2.5em}.wp-block-archives .wp-block-archives{border:0;margin:0}PKt\׽YYarchives/style.min.cssnu[.wp-block-archives{box-sizing:border-box}.wp-block-archives-dropdown label{display:block}PKt\Oyeearchives/style-rtl.cssnu[.wp-block-archives{ box-sizing:border-box; } .wp-block-archives-dropdown label{ display:block; }PKt\Upparchives/editor-rtl.cssnu[ul.wp-block-archives{ padding-right:2.5em; } .wp-block-archives .wp-block-archives{ border:0; margin:0; }PKt\Oyeearchives/style.cssnu[.wp-block-archives{ box-sizing:border-box; } .wp-block-archives-dropdown label{ display:block; }PKt\׽YYarchives/style-rtl.min.cssnu[.wp-block-archives{box-sizing:border-box}.wp-block-archives-dropdown label{display:block}PKt\Ҵaaarchives/editor-rtl.min.cssnu[ul.wp-block-archives{padding-right:2.5em}.wp-block-archives .wp-block-archives{border:0;margin:0}PKt\.'ooarchives/editor.cssnu[ul.wp-block-archives{ padding-left:2.5em; } .wp-block-archives .wp-block-archives{ border:0; margin:0; }PKt\22query-title.phpnu[ $align_class_name ) ); return sprintf( '<%1$s %2$s>%3$s', $tag_name, $wrapper_attributes, $title ); } /** * Registers the `core/query-title` block on the server. * * @since 5.8.0 */ function register_block_core_query_title() { register_block_type_from_metadata( __DIR__ . '/query-title', array( 'render_callback' => 'render_block_core_query_title', ) ); } add_action( 'init', 'register_block_core_query_title' ); PKt\8=site-tagline.phpnu[ $align_class_name ) ); if ( isset( $attributes['level'] ) && 0 !== $attributes['level'] ) { $tag_name = 'h' . (int) $attributes['level']; } return sprintf( '<%1$s %2$s>%3$s', $tag_name, $wrapper_attributes, $site_tagline ); } /** * Registers the `core/site-tagline` block on the server. * * @since 5.8.0 */ function register_block_core_site_tagline() { register_block_type_from_metadata( __DIR__ . '/site-tagline', array( 'render_callback' => 'render_block_core_site_tagline', ) ); } add_action( 'init', 'register_block_core_site_tagline' ); PKt\$ comments-title.phpnu[ $align_class_name ) ); $comments_count = get_comments_number(); /* translators: %s: Post title. */ $post_title = sprintf( __( '“%s”' ), get_the_title() ); $tag_name = 'h2'; if ( isset( $attributes['level'] ) ) { $tag_name = 'h' . $attributes['level']; } if ( '0' === $comments_count ) { return; } if ( $show_comments_count ) { if ( $show_post_title ) { if ( '1' === $comments_count ) { /* translators: %s: Post title. */ $comments_title = sprintf( __( 'One response to %s' ), $post_title ); } else { $comments_title = sprintf( /* translators: 1: Number of comments, 2: Post title. */ _n( '%1$s response to %2$s', '%1$s responses to %2$s', $comments_count ), number_format_i18n( $comments_count ), $post_title ); } } elseif ( '1' === $comments_count ) { $comments_title = __( 'One response' ); } else { $comments_title = sprintf( /* translators: %s: Number of comments. */ _n( '%s response', '%s responses', $comments_count ), number_format_i18n( $comments_count ) ); } } elseif ( $show_post_title ) { if ( '1' === $comments_count ) { /* translators: %s: Post title. */ $comments_title = sprintf( __( 'Response to %s' ), $post_title ); } else { /* translators: %s: Post title. */ $comments_title = sprintf( __( 'Responses to %s' ), $post_title ); } } elseif ( '1' === $comments_count ) { $comments_title = __( 'Response' ); } else { $comments_title = __( 'Responses' ); } return sprintf( '<%1$s id="comments" %2$s>%3$s', $tag_name, $wrapper_attributes, $comments_title ); } /** * Registers the `core/comments-title` block on the server. * * @since 6.0.0 */ function register_block_core_comments_title() { register_block_type_from_metadata( __DIR__ . '/comments-title', array( 'render_callback' => 'render_block_core_comments_title', ) ); } add_action( 'init', 'register_block_core_comments_title' ); PKt\query-no-results/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/query-no-results", "title": "No Results", "category": "theme", "description": "Contains the block elements used to render content when no query results are found.", "ancestor": [ "core/query" ], "textdomain": "default", "usesContext": [ "queryId", "query" ], "supports": { "align": true, "reusable": false, "html": false, "color": { "gradients": true, "link": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } } } PKt\tv$query-pagination-previous/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/query-pagination-previous", "title": "Previous Page", "category": "theme", "parent": [ "core/query-pagination" ], "description": "Displays the previous posts page link.", "textdomain": "default", "attributes": { "label": { "type": "string" } }, "usesContext": [ "queryId", "query", "paginationArrow", "showLabel", "enhancedPagination" ], "supports": { "reusable": false, "html": false, "color": { "gradients": true, "text": false, "__experimentalDefaultControls": { "background": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } } } PKt\dj4 4 post-date.phpnu[context['postId'] ) ) { return ''; } $post_ID = $block->context['postId']; if ( isset( $attributes['format'] ) && 'human-diff' === $attributes['format'] ) { $post_timestamp = get_post_timestamp( $post_ID ); if ( $post_timestamp > time() ) { // translators: %s: human-readable time difference. $formatted_date = sprintf( __( '%s from now' ), human_time_diff( $post_timestamp ) ); } else { // translators: %s: human-readable time difference. $formatted_date = sprintf( __( '%s ago' ), human_time_diff( $post_timestamp ) ); } } else { $formatted_date = get_the_date( empty( $attributes['format'] ) ? '' : $attributes['format'], $post_ID ); } $unformatted_date = esc_attr( get_the_date( 'c', $post_ID ) ); $classes = array(); if ( isset( $attributes['textAlign'] ) ) { $classes[] = 'has-text-align-' . $attributes['textAlign']; } if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { $classes[] = 'has-link-color'; } /* * If the "Display last modified date" setting is enabled, * only display the modified date if it is later than the publishing date. */ if ( isset( $attributes['displayType'] ) && 'modified' === $attributes['displayType'] ) { if ( get_the_modified_date( 'Ymdhi', $post_ID ) > get_the_date( 'Ymdhi', $post_ID ) ) { if ( isset( $attributes['format'] ) && 'human-diff' === $attributes['format'] ) { // translators: %s: human-readable time difference. $formatted_date = sprintf( __( '%s ago' ), human_time_diff( get_post_timestamp( $post_ID, 'modified' ) ) ); } else { $formatted_date = get_the_modified_date( empty( $attributes['format'] ) ? '' : $attributes['format'], $post_ID ); } $unformatted_date = esc_attr( get_the_modified_date( 'c', $post_ID ) ); $classes[] = 'wp-block-post-date__modified-date'; } else { return ''; } } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) ); if ( isset( $attributes['isLink'] ) && $attributes['isLink'] ) { $formatted_date = sprintf( '%2s', get_the_permalink( $post_ID ), $formatted_date ); } return sprintf( '
            ', $wrapper_attributes, $unformatted_date, $formatted_date ); } /** * Registers the `core/post-date` block on the server. * * @since 5.8.0 */ function register_block_core_post_date() { register_block_type_from_metadata( __DIR__ . '/post-date', array( 'render_callback' => 'render_block_core_post_date', ) ); } add_action( 'init', 'register_block_core_post_date' ); PKt\,} post-author-biography/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/post-author-biography", "title": "Author Biography", "category": "theme", "description": "The author biography.", "textdomain": "default", "attributes": { "textAlign": { "type": "string" } }, "usesContext": [ "postType", "postId" ], "example": { "viewportWidth": 350 }, "supports": { "spacing": { "margin": true, "padding": true }, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "style": "wp-block-post-author-biography" } PKt\lA+66#post-author-biography/style.min.cssnu[.wp-block-post-author-biography{box-sizing:border-box}PKt\(it;;#post-author-biography/style-rtl.cssnu[.wp-block-post-author-biography{ box-sizing:border-box; }PKt\(it;;post-author-biography/style.cssnu[.wp-block-post-author-biography{ box-sizing:border-box; }PKt\lA+66'post-author-biography/style-rtl.min.cssnu[.wp-block-post-author-biography{box-sizing:border-box}PKt\]V#comments-pagination-next/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/comments-pagination-next", "title": "Comments Next Page", "category": "theme", "parent": [ "core/comments-pagination" ], "description": "Displays the next comment's page link.", "textdomain": "default", "attributes": { "label": { "type": "string" } }, "usesContext": [ "postId", "comments/paginationArrow" ], "supports": { "reusable": false, "html": false, "color": { "gradients": true, "text": false, "__experimentalDefaultControls": { "background": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } } } PKt\sDavatar/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/avatar", "title": "Avatar", "category": "theme", "description": "Add a user’s avatar.", "textdomain": "default", "attributes": { "userId": { "type": "number" }, "size": { "type": "number", "default": 96 }, "isLink": { "type": "boolean", "default": false }, "linkTarget": { "type": "string", "default": "_self" } }, "usesContext": [ "postType", "postId", "commentId" ], "supports": { "html": false, "align": true, "alignWide": false, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "__experimentalBorder": { "__experimentalSkipSerialization": true, "radius": true, "width": true, "color": true, "style": true, "__experimentalDefaultControls": { "radius": true } }, "color": { "text": false, "background": false, "__experimentalDuotone": "img" }, "interactivity": { "clientNavigation": true } }, "selectors": { "border": ".wp-block-avatar img" }, "editorStyle": "wp-block-avatar-editor", "style": "wp-block-avatar" } PKt\Ϲ;wwavatar/editor.min.cssnu[.wp-block-avatar__image img{width:100%}.wp-block-avatar.aligncenter .components-resizable-box__container{margin:0 auto}PKt\Yavatar/style.min.cssnu[.wp-block-avatar{line-height:0}.wp-block-avatar,.wp-block-avatar img{box-sizing:border-box}.wp-block-avatar.aligncenter{text-align:center}PKt\X0avatar/style-rtl.cssnu[.wp-block-avatar{ line-height:0; } .wp-block-avatar,.wp-block-avatar img{ box-sizing:border-box; } .wp-block-avatar.aligncenter{ text-align:center; }PKt\T$avatar/editor-rtl.cssnu[.wp-block-avatar__image img{ width:100%; } .wp-block-avatar.aligncenter .components-resizable-box__container{ margin:0 auto; }PKt\X0avatar/style.cssnu[.wp-block-avatar{ line-height:0; } .wp-block-avatar,.wp-block-avatar img{ box-sizing:border-box; } .wp-block-avatar.aligncenter{ text-align:center; }PKt\Yavatar/style-rtl.min.cssnu[.wp-block-avatar{line-height:0}.wp-block-avatar,.wp-block-avatar img{box-sizing:border-box}.wp-block-avatar.aligncenter{text-align:center}PKt\Ϲ;wwavatar/editor-rtl.min.cssnu[.wp-block-avatar__image img{width:100%}.wp-block-avatar.aligncenter .components-resizable-box__container{margin:0 auto}PKt\T$avatar/editor.cssnu[.wp-block-avatar__image img{ width:100%; } .wp-block-avatar.aligncenter .components-resizable-box__container{ margin:0 auto; }PKt\Òy||code/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/code", "title": "Code", "category": "text", "description": "Display code snippets that respect your spacing and tabs.", "textdomain": "default", "attributes": { "content": { "type": "rich-text", "source": "rich-text", "selector": "code", "__unstablePreserveWhiteSpace": true } }, "supports": { "align": [ "wide" ], "anchor": true, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "spacing": { "margin": [ "top", "bottom" ], "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "width": true, "color": true } }, "color": { "text": true, "background": true, "gradients": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "interactivity": { "clientNavigation": true } }, "style": "wp-block-code" } PKt\p w$$code/editor.min.cssnu[.wp-block-code code{background:none}PKt\؊Kttcode/theme.min.cssnu[.wp-block-code{border:1px solid #ccc;border-radius:4px;font-family:Menlo,Consolas,monaco,monospace;padding:.8em 1em}PKt\kvЂcode/theme.cssnu[.wp-block-code{ border:1px solid #ccc; border-radius:4px; font-family:Menlo,Consolas,monaco,monospace; padding:.8em 1em; }PKt\?code/style.min.cssnu[.wp-block-code{box-sizing:border-box}.wp-block-code code{ /*!rtl:begin:ignore*/direction:ltr;display:block;font-family:inherit;overflow-wrap:break-word;text-align:initial;white-space:pre-wrap /*!rtl:end:ignore*/}PKt\code/style-rtl.cssnu[.wp-block-code{ box-sizing:border-box; } .wp-block-code code{ direction:ltr; display:block; font-family:inherit; overflow-wrap:break-word; text-align:initial; white-space:pre-wrap; }PKt\+))code/editor-rtl.cssnu[.wp-block-code code{ background:none; }PKt\kvЂcode/theme-rtl.cssnu[.wp-block-code{ border:1px solid #ccc; border-radius:4px; font-family:Menlo,Consolas,monaco,monospace; padding:.8em 1em; }PKt\code/style.cssnu[.wp-block-code{ box-sizing:border-box; } .wp-block-code code{ direction:ltr; display:block; font-family:inherit; overflow-wrap:break-word; text-align:initial; white-space:pre-wrap; }PKt\؊Kttcode/theme-rtl.min.cssnu[.wp-block-code{border:1px solid #ccc;border-radius:4px;font-family:Menlo,Consolas,monaco,monospace;padding:.8em 1em}PKt\ҿcode/style-rtl.min.cssnu[.wp-block-code{box-sizing:border-box}.wp-block-code code{direction:ltr;display:block;font-family:inherit;overflow-wrap:break-word;text-align:initial;white-space:pre-wrap}PKt\p w$$code/editor-rtl.min.cssnu[.wp-block-code code{background:none}PKt\+))code/editor.cssnu[.wp-block-code code{ background:none; }PKt\ǎ汳comment-author-name/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/comment-author-name", "title": "Comment Author Name", "category": "theme", "ancestor": [ "core/comment-template" ], "description": "Displays the name of the author of the comment.", "textdomain": "default", "attributes": { "isLink": { "type": "boolean", "default": true }, "linkTarget": { "type": "string", "default": "_self" }, "textAlign": { "type": "string" } }, "usesContext": [ "commentId" ], "supports": { "html": false, "spacing": { "margin": true, "padding": true }, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true, "link": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "style": "wp-block-comment-author-name" } PKt\44!comment-author-name/style.min.cssnu[.wp-block-comment-author-name{box-sizing:border-box}PKt\mk99!comment-author-name/style-rtl.cssnu[.wp-block-comment-author-name{ box-sizing:border-box; }PKt\mk99comment-author-name/style.cssnu[.wp-block-comment-author-name{ box-sizing:border-box; }PKt\44%comment-author-name/style-rtl.min.cssnu[.wp-block-comment-author-name{box-sizing:border-box}PKt\nήverse/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/verse", "title": "Verse", "category": "text", "description": "Insert poetry. Use special spacing formats. Or quote song lyrics.", "keywords": [ "poetry", "poem" ], "textdomain": "default", "attributes": { "content": { "type": "rich-text", "source": "rich-text", "selector": "pre", "__unstablePreserveWhiteSpace": true, "role": "content" }, "textAlign": { "type": "string" } }, "supports": { "anchor": true, "background": { "backgroundImage": true, "backgroundSize": true, "__experimentalDefaultControls": { "backgroundImage": true } }, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "dimensions": { "minHeight": true, "__experimentalDefaultControls": { "minHeight": false } }, "typography": { "fontSize": true, "__experimentalFontFamily": true, "lineHeight": true, "__experimentalFontStyle": true, "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalWritingMode": true, "__experimentalDefaultControls": { "fontSize": true } }, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "__experimentalBorder": { "radius": true, "width": true, "color": true, "style": true }, "interactivity": { "clientNavigation": true } }, "style": "wp-block-verse", "editorStyle": "wp-block-verse-editor" } PKt\p7eeverse/style.min.cssnu[pre.wp-block-verse{overflow:auto;white-space:pre-wrap}:where(pre.wp-block-verse){font-family:inherit}PKt\}]ttverse/style-rtl.cssnu[pre.wp-block-verse{ overflow:auto; white-space:pre-wrap; } :where(pre.wp-block-verse){ font-family:inherit; }PKt\}]ttverse/style.cssnu[pre.wp-block-verse{ overflow:auto; white-space:pre-wrap; } :where(pre.wp-block-verse){ font-family:inherit; }PKt\p7eeverse/style-rtl.min.cssnu[pre.wp-block-verse{overflow:auto;white-space:pre-wrap}:where(pre.wp-block-verse){font-family:inherit}PKt\G!Cjvideo/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/video", "title": "Video", "category": "media", "description": "Embed a video from your media library or upload a new one.", "keywords": [ "movie" ], "textdomain": "default", "attributes": { "autoplay": { "type": "boolean", "source": "attribute", "selector": "video", "attribute": "autoplay" }, "caption": { "type": "rich-text", "source": "rich-text", "selector": "figcaption", "role": "content" }, "controls": { "type": "boolean", "source": "attribute", "selector": "video", "attribute": "controls", "default": true }, "id": { "type": "number", "role": "content" }, "loop": { "type": "boolean", "source": "attribute", "selector": "video", "attribute": "loop" }, "muted": { "type": "boolean", "source": "attribute", "selector": "video", "attribute": "muted" }, "poster": { "type": "string", "source": "attribute", "selector": "video", "attribute": "poster" }, "preload": { "type": "string", "source": "attribute", "selector": "video", "attribute": "preload", "default": "metadata" }, "blob": { "type": "string", "role": "local" }, "src": { "type": "string", "source": "attribute", "selector": "video", "attribute": "src", "role": "content" }, "playsInline": { "type": "boolean", "source": "attribute", "selector": "video", "attribute": "playsinline" }, "tracks": { "role": "content", "type": "array", "items": { "type": "object" }, "default": [] } }, "supports": { "anchor": true, "align": true, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-video-editor", "style": "wp-block-video" } PKt\video/editor.min.cssnu[.wp-block[data-align=center]>.wp-block-video{text-align:center}.wp-block-video{position:relative}.wp-block-video.is-transient video{opacity:.3}.wp-block-video .components-spinner{left:50%;margin-left:-9px;margin-top:-9px;position:absolute;top:50%}.editor-video-poster-control .components-button{margin-right:8px}.block-library-video-tracks-editor{z-index:159990}.block-library-video-tracks-editor__track-list-track{padding-left:12px}.block-library-video-tracks-editor__single-track-editor-kind-select{max-width:240px}.block-library-video-tracks-editor__single-track-editor-edit-track-label,.block-library-video-tracks-editor__tracks-informative-message-title{color:#757575;display:block;font-size:11px;font-weight:500;margin-top:4px;text-transform:uppercase}.block-library-video-tracks-editor>.components-popover__content{width:360px}.block-library-video-tracks-editor__add-tracks-container .components-menu-group__label,.block-library-video-tracks-editor__track-list .components-menu-group__label{padding:0}.block-library-video-tracks-editor__tracks-informative-message{padding:8px}.block-library-video-tracks-editor__tracks-informative-message-description{margin-bottom:0}PKt\ذvideo/theme.min.cssnu[.wp-block-video :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-video :where(figcaption){color:#ffffffa6}.wp-block-video{margin:0 0 1em}PKt\video/theme.cssnu[.wp-block-video :where(figcaption){ color:#555; font-size:13px; text-align:center; } .is-dark-theme .wp-block-video :where(figcaption){ color:#ffffffa6; } .wp-block-video{ margin:0 0 1em; }PKt\evideo/style.min.cssnu[.wp-block-video{box-sizing:border-box}.wp-block-video video{vertical-align:middle;width:100%}@supports (position:sticky){.wp-block-video [poster]{object-fit:cover}}.wp-block-video.aligncenter{text-align:center}.wp-block-video :where(figcaption){margin-bottom:1em;margin-top:.5em}PKt\ovqBBvideo/style-rtl.cssnu[.wp-block-video{ box-sizing:border-box; } .wp-block-video video{ vertical-align:middle; width:100%; } @supports (position:sticky){ .wp-block-video [poster]{ object-fit:cover; } } .wp-block-video.aligncenter{ text-align:center; } .wp-block-video :where(figcaption){ margin-bottom:1em; margin-top:.5em; }PKt\<]  video/editor-rtl.cssnu[.wp-block[data-align=center]>.wp-block-video{ text-align:center; } .wp-block-video{ position:relative; } .wp-block-video.is-transient video{ opacity:.3; } .wp-block-video .components-spinner{ margin-right:-9px; margin-top:-9px; position:absolute; right:50%; top:50%; } .editor-video-poster-control .components-button{ margin-left:8px; } .block-library-video-tracks-editor{ z-index:159990; } .block-library-video-tracks-editor__track-list-track{ padding-right:12px; } .block-library-video-tracks-editor__single-track-editor-kind-select{ max-width:240px; } .block-library-video-tracks-editor__single-track-editor-edit-track-label,.block-library-video-tracks-editor__tracks-informative-message-title{ color:#757575; display:block; font-size:11px; font-weight:500; margin-top:4px; text-transform:uppercase; } .block-library-video-tracks-editor>.components-popover__content{ width:360px; } .block-library-video-tracks-editor__add-tracks-container .components-menu-group__label,.block-library-video-tracks-editor__track-list .components-menu-group__label{ padding:0; } .block-library-video-tracks-editor__tracks-informative-message{ padding:8px; } .block-library-video-tracks-editor__tracks-informative-message-description{ margin-bottom:0; }PKt\video/theme-rtl.cssnu[.wp-block-video :where(figcaption){ color:#555; font-size:13px; text-align:center; } .is-dark-theme .wp-block-video :where(figcaption){ color:#ffffffa6; } .wp-block-video{ margin:0 0 1em; }PKt\ovqBBvideo/style.cssnu[.wp-block-video{ box-sizing:border-box; } .wp-block-video video{ vertical-align:middle; width:100%; } @supports (position:sticky){ .wp-block-video [poster]{ object-fit:cover; } } .wp-block-video.aligncenter{ text-align:center; } .wp-block-video :where(figcaption){ margin-bottom:1em; margin-top:.5em; }PKt\ذvideo/theme-rtl.min.cssnu[.wp-block-video :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-video :where(figcaption){color:#ffffffa6}.wp-block-video{margin:0 0 1em}PKt\evideo/style-rtl.min.cssnu[.wp-block-video{box-sizing:border-box}.wp-block-video video{vertical-align:middle;width:100%}@supports (position:sticky){.wp-block-video [poster]{object-fit:cover}}.wp-block-video.aligncenter{text-align:center}.wp-block-video :where(figcaption){margin-bottom:1em;margin-top:.5em}PKt\mgQvideo/editor-rtl.min.cssnu[.wp-block[data-align=center]>.wp-block-video{text-align:center}.wp-block-video{position:relative}.wp-block-video.is-transient video{opacity:.3}.wp-block-video .components-spinner{margin-right:-9px;margin-top:-9px;position:absolute;right:50%;top:50%}.editor-video-poster-control .components-button{margin-left:8px}.block-library-video-tracks-editor{z-index:159990}.block-library-video-tracks-editor__track-list-track{padding-right:12px}.block-library-video-tracks-editor__single-track-editor-kind-select{max-width:240px}.block-library-video-tracks-editor__single-track-editor-edit-track-label,.block-library-video-tracks-editor__tracks-informative-message-title{color:#757575;display:block;font-size:11px;font-weight:500;margin-top:4px;text-transform:uppercase}.block-library-video-tracks-editor>.components-popover__content{width:360px}.block-library-video-tracks-editor__add-tracks-container .components-menu-group__label,.block-library-video-tracks-editor__track-list .components-menu-group__label{padding:0}.block-library-video-tracks-editor__tracks-informative-message{padding:8px}.block-library-video-tracks-editor__tracks-informative-message-description{margin-bottom:0}PKt\5pvideo/editor.cssnu[.wp-block[data-align=center]>.wp-block-video{ text-align:center; } .wp-block-video{ position:relative; } .wp-block-video.is-transient video{ opacity:.3; } .wp-block-video .components-spinner{ left:50%; margin-left:-9px; margin-top:-9px; position:absolute; top:50%; } .editor-video-poster-control .components-button{ margin-right:8px; } .block-library-video-tracks-editor{ z-index:159990; } .block-library-video-tracks-editor__track-list-track{ padding-left:12px; } .block-library-video-tracks-editor__single-track-editor-kind-select{ max-width:240px; } .block-library-video-tracks-editor__single-track-editor-edit-track-label,.block-library-video-tracks-editor__tracks-informative-message-title{ color:#757575; display:block; font-size:11px; font-weight:500; margin-top:4px; text-transform:uppercase; } .block-library-video-tracks-editor>.components-popover__content{ width:360px; } .block-library-video-tracks-editor__add-tracks-container .components-menu-group__label,.block-library-video-tracks-editor__track-list .components-menu-group__label{ padding:0; } .block-library-video-tracks-editor__tracks-informative-message{ padding:8px; } .block-library-video-tracks-editor__tracks-informative-message-description{ margin-bottom:0; }PKt\\.UUpage-list-item/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/page-list-item", "title": "Page List Item", "category": "widgets", "parent": [ "core/page-list" ], "description": "Displays a page inside a list of all pages.", "keywords": [ "page", "menu", "navigation" ], "textdomain": "default", "attributes": { "id": { "type": "number" }, "label": { "type": "string" }, "title": { "type": "string" }, "link": { "type": "string" }, "hasChildren": { "type": "boolean" } }, "usesContext": [ "textColor", "customTextColor", "backgroundColor", "customBackgroundColor", "overlayTextColor", "customOverlayTextColor", "overlayBackgroundColor", "customOverlayBackgroundColor", "fontSize", "customFontSize", "showSubmenuIcon", "style", "openSubmenusOnClick" ], "supports": { "reusable": false, "html": false, "lock": false, "inserter": false, "__experimentalToolbar": false, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-page-list-editor", "style": "wp-block-page-list" } PKt\JWiimissing/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/missing", "title": "Unsupported", "category": "text", "description": "Your site doesn’t include support for this block.", "textdomain": "default", "attributes": { "originalName": { "type": "string" }, "originalUndelimitedContent": { "type": "string" }, "originalContent": { "type": "string", "source": "raw" } }, "supports": { "className": false, "customClassName": false, "inserter": false, "html": false, "reusable": false, "interactivity": { "clientNavigation": true } } } PKt\rquery-pagination-numbers.phpnu[context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination']; $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; $max_page = isset( $block->context['query']['pages'] ) ? (int) $block->context['query']['pages'] : 0; $wrapper_attributes = get_block_wrapper_attributes(); $content = ''; global $wp_query; $mid_size = isset( $block->attributes['midSize'] ) ? (int) $block->attributes['midSize'] : null; if ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ) { // Take into account if we have set a bigger `max page` // than what the query has. $total = ! $max_page || $max_page > $wp_query->max_num_pages ? $wp_query->max_num_pages : $max_page; $paginate_args = array( 'prev_next' => false, 'total' => $total, ); if ( null !== $mid_size ) { $paginate_args['mid_size'] = $mid_size; } $content = paginate_links( $paginate_args ); } else { $block_query = new WP_Query( build_query_vars_from_query_block( $block, $page ) ); // `paginate_links` works with the global $wp_query, so we have to // temporarily switch it with our custom query. $prev_wp_query = $wp_query; $wp_query = $block_query; $total = ! $max_page || $max_page > $wp_query->max_num_pages ? $wp_query->max_num_pages : $max_page; $paginate_args = array( 'base' => '%_%', 'format' => "?$page_key=%#%", 'current' => max( 1, $page ), 'total' => $total, 'prev_next' => false, ); if ( null !== $mid_size ) { $paginate_args['mid_size'] = $mid_size; } if ( 1 !== $page ) { /** * `paginate_links` doesn't use the provided `format` when the page is `1`. * This is great for the main query as it removes the extra query params * making the URL shorter, but in the case of multiple custom queries is * problematic. It results in returning an empty link which ends up with * a link to the current page. * * A way to address this is to add a `fake` query arg with no value that * is the same for all custom queries. This way the link is not empty and * preserves all the other existent query args. * * @see https://developer.wordpress.org/reference/functions/paginate_links/ * * The proper fix of this should be in core. Track Ticket: * @see https://core.trac.wordpress.org/ticket/53868 * * TODO: After two WP versions (starting from the WP version the core patch landed), * we should remove this and call `paginate_links` with the proper new arg. */ $paginate_args['add_args'] = array( 'cst' => '' ); } // We still need to preserve `paged` query param if exists, as is used // for Queries that inherit from global context. $paged = empty( $_GET['paged'] ) ? null : (int) $_GET['paged']; if ( $paged ) { $paginate_args['add_args'] = array( 'paged' => $paged ); } $content = paginate_links( $paginate_args ); wp_reset_postdata(); // Restore original Post Data. $wp_query = $prev_wp_query; } if ( empty( $content ) ) { return ''; } if ( $enhanced_pagination ) { $p = new WP_HTML_Tag_Processor( $content ); $tag_index = 0; while ( $p->next_tag( array( 'class_name' => 'page-numbers' ) ) ) { if ( null === $p->get_attribute( 'data-wp-key' ) ) { $p->set_attribute( 'data-wp-key', 'index-' . $tag_index++ ); } if ( 'A' === $p->get_tag() ) { $p->set_attribute( 'data-wp-on--click', 'core/query::actions.navigate' ); } } $content = $p->get_updated_html(); } return sprintf( '
            %2$s
            ', $wrapper_attributes, $content ); } /** * Registers the `core/query-pagination-numbers` block on the server. * * @since 5.8.0 */ function register_block_core_query_pagination_numbers() { register_block_type_from_metadata( __DIR__ . '/query-pagination-numbers', array( 'render_callback' => 'render_block_core_query_pagination_numbers', ) ); } add_action( 'init', 'register_block_core_query_pagination_numbers' ); PKt\|?ʒ error_lognu[[25-Feb-2026 03:28:05 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/archives.php:122 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/archives.php on line 122 [25-Feb-2026 03:28:07 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/latest-posts.php:237 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/latest-posts.php on line 237 [25-Feb-2026 03:28:11 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/navigation.php:1173 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/navigation.php on line 1173 [06-Mar-2026 04:35:34 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_filter() in /home/smkb8269/public_html/wp-includes/blocks/gallery.php:35 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/gallery.php on line 35 [20-Mar-2026 11:54:49 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/latest-posts.php:237 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/latest-posts.php on line 237 [20-Mar-2026 11:54:49 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/rss.php:126 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/rss.php on line 126 [20-Mar-2026 11:54:49 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/search.php:210 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/search.php on line 210 [28-Mar-2026 10:00:33 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/latest-posts.php:237 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/latest-posts.php on line 237 [28-Mar-2026 10:00:34 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/rss.php:126 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/rss.php on line 126 [28-Mar-2026 10:00:34 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/search.php:210 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/search.php on line 210 [30-Mar-2026 09:31:01 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/latest-posts.php:237 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/latest-posts.php on line 237 [30-Mar-2026 09:31:01 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/rss.php:126 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/rss.php on line 126 [30-Mar-2026 09:31:01 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/search.php:210 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/search.php on line 210 [04-Apr-2026 16:30:54 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/latest-posts.php:237 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/latest-posts.php on line 237 [04-Apr-2026 16:30:54 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/rss.php:126 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/rss.php on line 126 [04-Apr-2026 16:30:55 Asia/Jakarta] PHP Fatal error: Uncaught Error: Call to undefined function add_action() in /home/smkb8269/public_html/wp-includes/blocks/search.php:210 Stack trace: #0 {main} thrown in /home/smkb8269/public_html/wp-includes/blocks/search.php on line 210 PKt\Ox)rss.phpnu[
            ' . __( 'Adding an RSS feed to this site’s homepage is not supported, as it could lead to a loop that slows down your site. Try using another block, like the Latest Posts block, to list posts from the site.' ) . '
            '; } $rss = fetch_feed( $attributes['feedURL'] ); if ( is_wp_error( $rss ) ) { return '
            ' . __( 'RSS Error:' ) . ' ' . esc_html( $rss->get_error_message() ) . '
            '; } if ( ! $rss->get_item_quantity() ) { return '
            ' . __( 'An error has occurred, which probably means the feed is down. Try again later.' ) . '
            '; } $rss_items = $rss->get_items( 0, $attributes['itemsToShow'] ); $list_items = ''; foreach ( $rss_items as $item ) { $title = esc_html( trim( strip_tags( $item->get_title() ) ) ); if ( empty( $title ) ) { $title = __( '(no title)' ); } $link = $item->get_link(); $link = esc_url( $link ); if ( $link ) { $title = "{$title}"; } $title = "
            {$title}
            "; $date = ''; if ( $attributes['displayDate'] ) { $date = $item->get_date( 'U' ); if ( $date ) { $date = sprintf( ' ', esc_attr( date_i18n( 'c', $date ) ), esc_attr( date_i18n( get_option( 'date_format' ), $date ) ) ); } } $author = ''; if ( $attributes['displayAuthor'] ) { $author = $item->get_author(); if ( is_object( $author ) ) { $author = $author->get_name(); if ( ! empty( $author ) ) { $author = '' . sprintf( /* translators: byline. %s: author. */ __( 'by %s' ), esc_html( strip_tags( $author ) ) ) . ''; } } } $excerpt = ''; $description = $item->get_description(); if ( $attributes['displayExcerpt'] && ! empty( $description ) ) { $excerpt = html_entity_decode( $description, ENT_QUOTES, get_option( 'blog_charset' ) ); $excerpt = esc_attr( wp_trim_words( $excerpt, $attributes['excerptLength'], ' […]' ) ); // Change existing [...] to […]. if ( '[...]' === substr( $excerpt, -5 ) ) { $excerpt = substr( $excerpt, 0, -5 ) . '[…]'; } $excerpt = '
            ' . esc_html( $excerpt ) . '
            '; } $list_items .= "
          • {$title}{$date}{$author}{$excerpt}
          • "; } $classnames = array(); if ( isset( $attributes['blockLayout'] ) && 'grid' === $attributes['blockLayout'] ) { $classnames[] = 'is-grid'; } if ( isset( $attributes['columns'] ) && 'grid' === $attributes['blockLayout'] ) { $classnames[] = 'columns-' . $attributes['columns']; } if ( $attributes['displayDate'] ) { $classnames[] = 'has-dates'; } if ( $attributes['displayAuthor'] ) { $classnames[] = 'has-authors'; } if ( $attributes['displayExcerpt'] ) { $classnames[] = 'has-excerpts'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classnames ) ) ); return sprintf( '
              %s
            ', $wrapper_attributes, $list_items ); } /** * Registers the `core/rss` block on server. * * @since 5.2.0 */ function register_block_core_rss() { register_block_type_from_metadata( __DIR__ . '/rss', array( 'render_callback' => 'render_block_core_rss', ) ); } add_action( 'init', 'register_block_core_rss' ); PKt\;lsearch/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/search", "title": "Search", "category": "widgets", "description": "Help visitors find your content.", "keywords": [ "find" ], "textdomain": "default", "attributes": { "label": { "type": "string", "role": "content" }, "showLabel": { "type": "boolean", "default": true }, "placeholder": { "type": "string", "default": "", "role": "content" }, "width": { "type": "number" }, "widthUnit": { "type": "string" }, "buttonText": { "type": "string", "role": "content" }, "buttonPosition": { "type": "string", "default": "button-outside" }, "buttonUseIcon": { "type": "boolean", "default": false }, "query": { "type": "object", "default": {} }, "isSearchFieldHidden": { "type": "boolean", "default": false } }, "supports": { "align": [ "left", "center", "right" ], "color": { "gradients": true, "__experimentalSkipSerialization": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "interactivity": true, "typography": { "__experimentalSkipSerialization": true, "__experimentalSelector": ".wp-block-search__label, .wp-block-search__input, .wp-block-search__button", "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "__experimentalBorder": { "color": true, "radius": true, "width": true, "__experimentalSkipSerialization": true, "__experimentalDefaultControls": { "color": true, "radius": true, "width": true } }, "spacing": { "margin": true }, "html": false }, "editorStyle": "wp-block-search-editor", "style": "wp-block-search" } PKt\+FFsearch/editor.min.cssnu[.wp-block[data-align=center] .wp-block-search .wp-block-search__inside-wrapper{margin:auto}.wp-block-search :where(.wp-block-search__button){align-items:center;border-radius:initial;display:flex;height:auto;justify-content:center;text-align:center}.wp-block-search__inspector-controls .components-base-control{margin-bottom:0}PKt\{e~~search/theme.min.cssnu[.wp-block-search .wp-block-search__label{font-weight:700}.wp-block-search__button{border:1px solid #ccc;padding:.375em .625em}PKt\ǠTTsearch/view.min.asset.phpnu[ array(), 'version' => '765a40956d200c79d99e'); PKt\_ search/view.jsnu[import * as __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__ from "@wordpress/interactivity"; /******/ // The require scope /******/ var __webpack_require__ = {}; /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; ;// external "@wordpress/interactivity" var x = (y) => { var x = {}; __webpack_require__.d(x, y); return x } var y = (x) => (() => (x)) const interactivity_namespaceObject = x({ ["getContext"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getContext), ["getElement"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getElement), ["store"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.store), ["withSyncEvent"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.withSyncEvent) }); ;// ./node_modules/@wordpress/block-library/build-module/search/view.js /** * WordPress dependencies */ const { actions } = (0,interactivity_namespaceObject.store)('core/search', { state: { get ariaLabel() { const { isSearchInputVisible, ariaLabelCollapsed, ariaLabelExpanded } = (0,interactivity_namespaceObject.getContext)(); return isSearchInputVisible ? ariaLabelExpanded : ariaLabelCollapsed; }, get ariaControls() { const { isSearchInputVisible, inputId } = (0,interactivity_namespaceObject.getContext)(); return isSearchInputVisible ? null : inputId; }, get type() { const { isSearchInputVisible } = (0,interactivity_namespaceObject.getContext)(); return isSearchInputVisible ? 'submit' : 'button'; }, get tabindex() { const { isSearchInputVisible } = (0,interactivity_namespaceObject.getContext)(); return isSearchInputVisible ? '0' : '-1'; } }, actions: { openSearchInput: (0,interactivity_namespaceObject.withSyncEvent)(event => { const ctx = (0,interactivity_namespaceObject.getContext)(); const { ref } = (0,interactivity_namespaceObject.getElement)(); if (!ctx.isSearchInputVisible) { event.preventDefault(); ctx.isSearchInputVisible = true; ref.parentElement.querySelector('input').focus(); } }), closeSearchInput() { const ctx = (0,interactivity_namespaceObject.getContext)(); ctx.isSearchInputVisible = false; }, handleSearchKeydown(event) { const { ref } = (0,interactivity_namespaceObject.getElement)(); // If Escape close the menu. if (event?.key === 'Escape') { actions.closeSearchInput(); ref.querySelector('button').focus(); } }, handleSearchFocusout(event) { const { ref } = (0,interactivity_namespaceObject.getElement)(); // If focus is outside search form, and in the document, close menu // event.target === The element losing focus // event.relatedTarget === The element receiving focus (if any) // When focusout is outside the document, // `window.document.activeElement` doesn't change. if (!ref.contains(event.relatedTarget) && event.target !== window.document.activeElement) { actions.closeSearchInput(); } } } }, { lock: true }); PKt\esearch/theme.cssnu[.wp-block-search .wp-block-search__label{ font-weight:700; } .wp-block-search__button{ border:1px solid #ccc; padding:.375em .625em; }PKt\'W W search/style.min.cssnu[.wp-block-search__button{margin-left:10px;word-break:normal}.wp-block-search__button.has-icon{line-height:0}.wp-block-search__button svg{height:1.25em;min-height:24px;min-width:24px;width:1.25em;fill:currentColor;vertical-align:text-bottom}:where(.wp-block-search__button){border:1px solid #ccc;padding:6px 10px}.wp-block-search__inside-wrapper{display:flex;flex:auto;flex-wrap:nowrap;max-width:100%}.wp-block-search__label{width:100%}.wp-block-search__input{appearance:none;border:1px solid #949494;flex-grow:1;margin-left:0;margin-right:0;min-width:3rem;padding:8px;text-decoration:unset!important}.wp-block-search.wp-block-search__button-only .wp-block-search__button{box-sizing:border-box;display:flex;flex-shrink:0;justify-content:center;margin-left:0;max-width:100%}.wp-block-search.wp-block-search__button-only .wp-block-search__inside-wrapper{min-width:0!important;transition-property:width}.wp-block-search.wp-block-search__button-only .wp-block-search__input{flex-basis:100%;transition-duration:.3s}.wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden,.wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden .wp-block-search__inside-wrapper{overflow:hidden}.wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden .wp-block-search__input{border-left-width:0!important;border-right-width:0!important;flex-basis:0;flex-grow:0;margin:0;min-width:0!important;padding-left:0!important;padding-right:0!important;width:0!important}:where(.wp-block-search__input){font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;text-transform:inherit}:where(.wp-block-search__button-inside .wp-block-search__inside-wrapper){border:1px solid #949494;box-sizing:border-box;padding:4px}:where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) .wp-block-search__input{border:none;border-radius:0;padding:0 4px}:where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) .wp-block-search__input:focus{outline:none}:where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) :where(.wp-block-search__button){padding:4px 8px}.wp-block-search.aligncenter .wp-block-search__inside-wrapper{margin:auto}.wp-block[data-align=right] .wp-block-search.wp-block-search__button-only .wp-block-search__inside-wrapper{float:right}PKt\I@TTsearch/view.asset.phpnu[ array(), 'version' => '2a0784014283afdd3c25'); PKt\ X X search/style-rtl.cssnu[.wp-block-search__button{ margin-right:10px; word-break:normal; } .wp-block-search__button.has-icon{ line-height:0; } .wp-block-search__button svg{ height:1.25em; min-height:24px; min-width:24px; width:1.25em; fill:currentColor; vertical-align:text-bottom; } :where(.wp-block-search__button){ border:1px solid #ccc; padding:6px 10px; } .wp-block-search__inside-wrapper{ display:flex; flex:auto; flex-wrap:nowrap; max-width:100%; } .wp-block-search__label{ width:100%; } .wp-block-search__input{ appearance:none; border:1px solid #949494; flex-grow:1; margin-left:0; margin-right:0; min-width:3rem; padding:8px; text-decoration:unset !important; } .wp-block-search.wp-block-search__button-only .wp-block-search__button{ box-sizing:border-box; display:flex; flex-shrink:0; justify-content:center; margin-right:0; max-width:100%; } .wp-block-search.wp-block-search__button-only .wp-block-search__inside-wrapper{ min-width:0 !important; transition-property:width; } .wp-block-search.wp-block-search__button-only .wp-block-search__input{ flex-basis:100%; transition-duration:.3s; } .wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden,.wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden .wp-block-search__inside-wrapper{ overflow:hidden; } .wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden .wp-block-search__input{ border-left-width:0 !important; border-right-width:0 !important; flex-basis:0; flex-grow:0; margin:0; min-width:0 !important; padding-left:0 !important; padding-right:0 !important; width:0 !important; } :where(.wp-block-search__input){ font-family:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; letter-spacing:inherit; line-height:inherit; text-transform:inherit; } :where(.wp-block-search__button-inside .wp-block-search__inside-wrapper){ border:1px solid #949494; box-sizing:border-box; padding:4px; } :where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) .wp-block-search__input{ border:none; border-radius:0; padding:0 4px; } :where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) .wp-block-search__input:focus{ outline:none; } :where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) :where(.wp-block-search__button){ padding:4px 8px; } .wp-block-search.aligncenter .wp-block-search__inside-wrapper{ margin:auto; } .wp-block[data-align=right] .wp-block-search.wp-block-search__button-only .wp-block-search__inside-wrapper{ float:left; }PKt\pBphhsearch/editor-rtl.cssnu[.wp-block[data-align=center] .wp-block-search .wp-block-search__inside-wrapper{ margin:auto; } .wp-block-search :where(.wp-block-search__button){ align-items:center; border-radius:initial; display:flex; height:auto; justify-content:center; text-align:center; } .wp-block-search__inspector-controls .components-base-control{ margin-bottom:0; }PKt\esearch/theme-rtl.cssnu[.wp-block-search .wp-block-search__label{ font-weight:700; } .wp-block-search__button{ border:1px solid #ccc; padding:.375em .625em; }PKt\RQ^W W search/style.cssnu[.wp-block-search__button{ margin-left:10px; word-break:normal; } .wp-block-search__button.has-icon{ line-height:0; } .wp-block-search__button svg{ height:1.25em; min-height:24px; min-width:24px; width:1.25em; fill:currentColor; vertical-align:text-bottom; } :where(.wp-block-search__button){ border:1px solid #ccc; padding:6px 10px; } .wp-block-search__inside-wrapper{ display:flex; flex:auto; flex-wrap:nowrap; max-width:100%; } .wp-block-search__label{ width:100%; } .wp-block-search__input{ appearance:none; border:1px solid #949494; flex-grow:1; margin-left:0; margin-right:0; min-width:3rem; padding:8px; text-decoration:unset !important; } .wp-block-search.wp-block-search__button-only .wp-block-search__button{ box-sizing:border-box; display:flex; flex-shrink:0; justify-content:center; margin-left:0; max-width:100%; } .wp-block-search.wp-block-search__button-only .wp-block-search__inside-wrapper{ min-width:0 !important; transition-property:width; } .wp-block-search.wp-block-search__button-only .wp-block-search__input{ flex-basis:100%; transition-duration:.3s; } .wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden,.wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden .wp-block-search__inside-wrapper{ overflow:hidden; } .wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden .wp-block-search__input{ border-left-width:0 !important; border-right-width:0 !important; flex-basis:0; flex-grow:0; margin:0; min-width:0 !important; padding-left:0 !important; padding-right:0 !important; width:0 !important; } :where(.wp-block-search__input){ font-family:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; letter-spacing:inherit; line-height:inherit; text-transform:inherit; } :where(.wp-block-search__button-inside .wp-block-search__inside-wrapper){ border:1px solid #949494; box-sizing:border-box; padding:4px; } :where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) .wp-block-search__input{ border:none; border-radius:0; padding:0 4px; } :where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) .wp-block-search__input:focus{ outline:none; } :where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) :where(.wp-block-search__button){ padding:4px 8px; } .wp-block-search.aligncenter .wp-block-search__inside-wrapper{ margin:auto; } .wp-block[data-align=right] .wp-block-search.wp-block-search__button-only .wp-block-search__inside-wrapper{ float:right; }PKt\{e~~search/theme-rtl.min.cssnu[.wp-block-search .wp-block-search__label{font-weight:700}.wp-block-search__button{border:1px solid #ccc;padding:.375em .625em}PKt\UJYYsearch/view.min.jsnu[import*as e from"@wordpress/interactivity";var t={d:(e,n)=>{for(var r in n)t.o(n,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:n[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const n=(e=>{var n={};return t.d(n,e),n})({getContext:()=>e.getContext,getElement:()=>e.getElement,store:()=>e.store,withSyncEvent:()=>e.withSyncEvent}),{actions:r}=(0,n.store)("core/search",{state:{get ariaLabel(){const{isSearchInputVisible:e,ariaLabelCollapsed:t,ariaLabelExpanded:r}=(0,n.getContext)();return e?r:t},get ariaControls(){const{isSearchInputVisible:e,inputId:t}=(0,n.getContext)();return e?null:t},get type(){const{isSearchInputVisible:e}=(0,n.getContext)();return e?"submit":"button"},get tabindex(){const{isSearchInputVisible:e}=(0,n.getContext)();return e?"0":"-1"}},actions:{openSearchInput:(0,n.withSyncEvent)((e=>{const t=(0,n.getContext)(),{ref:r}=(0,n.getElement)();t.isSearchInputVisible||(e.preventDefault(),t.isSearchInputVisible=!0,r.parentElement.querySelector("input").focus())})),closeSearchInput(){(0,n.getContext)().isSearchInputVisible=!1},handleSearchKeydown(e){const{ref:t}=(0,n.getElement)();"Escape"===e?.key&&(r.closeSearchInput(),t.querySelector("button").focus())},handleSearchFocusout(e){const{ref:t}=(0,n.getElement)();t.contains(e.relatedTarget)||e.target===window.document.activeElement||r.closeSearchInput()}}},{lock:!0});PKt\C*BX X search/style-rtl.min.cssnu[.wp-block-search__button{margin-right:10px;word-break:normal}.wp-block-search__button.has-icon{line-height:0}.wp-block-search__button svg{height:1.25em;min-height:24px;min-width:24px;width:1.25em;fill:currentColor;vertical-align:text-bottom}:where(.wp-block-search__button){border:1px solid #ccc;padding:6px 10px}.wp-block-search__inside-wrapper{display:flex;flex:auto;flex-wrap:nowrap;max-width:100%}.wp-block-search__label{width:100%}.wp-block-search__input{appearance:none;border:1px solid #949494;flex-grow:1;margin-left:0;margin-right:0;min-width:3rem;padding:8px;text-decoration:unset!important}.wp-block-search.wp-block-search__button-only .wp-block-search__button{box-sizing:border-box;display:flex;flex-shrink:0;justify-content:center;margin-right:0;max-width:100%}.wp-block-search.wp-block-search__button-only .wp-block-search__inside-wrapper{min-width:0!important;transition-property:width}.wp-block-search.wp-block-search__button-only .wp-block-search__input{flex-basis:100%;transition-duration:.3s}.wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden,.wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden .wp-block-search__inside-wrapper{overflow:hidden}.wp-block-search.wp-block-search__button-only.wp-block-search__searchfield-hidden .wp-block-search__input{border-left-width:0!important;border-right-width:0!important;flex-basis:0;flex-grow:0;margin:0;min-width:0!important;padding-left:0!important;padding-right:0!important;width:0!important}:where(.wp-block-search__input){font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;text-transform:inherit}:where(.wp-block-search__button-inside .wp-block-search__inside-wrapper){border:1px solid #949494;box-sizing:border-box;padding:4px}:where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) .wp-block-search__input{border:none;border-radius:0;padding:0 4px}:where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) .wp-block-search__input:focus{outline:none}:where(.wp-block-search__button-inside .wp-block-search__inside-wrapper) :where(.wp-block-search__button){padding:4px 8px}.wp-block-search.aligncenter .wp-block-search__inside-wrapper{margin:auto}.wp-block[data-align=right] .wp-block-search.wp-block-search__button-only .wp-block-search__inside-wrapper{float:left}PKt\+FFsearch/editor-rtl.min.cssnu[.wp-block[data-align=center] .wp-block-search .wp-block-search__inside-wrapper{margin:auto}.wp-block-search :where(.wp-block-search__button){align-items:center;border-radius:initial;display:flex;height:auto;justify-content:center;text-align:center}.wp-block-search__inspector-controls .components-base-control{margin-bottom:0}PKt\pBphhsearch/editor.cssnu[.wp-block[data-align=center] .wp-block-search .wp-block-search__inside-wrapper{ margin:auto; } .wp-block-search :where(.wp-block-search__button){ align-items:center; border-radius:initial; display:flex; height:auto; justify-content:center; text-align:center; } .wp-block-search__inspector-controls .components-base-control{ margin-bottom:0; }PKt\+5+5 page-list.phpnu[ array(), 'inline_styles' => '', 'overlay_css_classes' => array(), 'overlay_inline_styles' => '', ); // Text color. $has_named_text_color = array_key_exists( 'textColor', $context ); $has_picked_text_color = array_key_exists( 'customTextColor', $context ); $has_custom_text_color = isset( $context['style']['color']['text'] ); // If has text color. if ( $has_custom_text_color || $has_picked_text_color || $has_named_text_color ) { // Add has-text-color class. $colors['css_classes'][] = 'has-text-color'; } if ( $has_named_text_color ) { // Add the color class. $colors['css_classes'][] = sprintf( 'has-%s-color', _wp_to_kebab_case( $context['textColor'] ) ); } elseif ( $has_picked_text_color ) { $colors['inline_styles'] .= sprintf( 'color: %s;', $context['customTextColor'] ); } elseif ( $has_custom_text_color ) { // Add the custom color inline style. $colors['inline_styles'] .= sprintf( 'color: %s;', $context['style']['color']['text'] ); } // Background color. $has_named_background_color = array_key_exists( 'backgroundColor', $context ); $has_picked_background_color = array_key_exists( 'customBackgroundColor', $context ); $has_custom_background_color = isset( $context['style']['color']['background'] ); // If has background color. if ( $has_custom_background_color || $has_picked_background_color || $has_named_background_color ) { // Add has-background class. $colors['css_classes'][] = 'has-background'; } if ( $has_named_background_color ) { // Add the background-color class. $colors['css_classes'][] = sprintf( 'has-%s-background-color', _wp_to_kebab_case( $context['backgroundColor'] ) ); } elseif ( $has_picked_background_color ) { $colors['inline_styles'] .= sprintf( 'background-color: %s;', $context['customBackgroundColor'] ); } elseif ( $has_custom_background_color ) { // Add the custom background-color inline style. $colors['inline_styles'] .= sprintf( 'background-color: %s;', $context['style']['color']['background'] ); } // Overlay text color. $has_named_overlay_text_color = array_key_exists( 'overlayTextColor', $context ); $has_picked_overlay_text_color = array_key_exists( 'customOverlayTextColor', $context ); // If it has a text color. if ( $has_named_overlay_text_color || $has_picked_overlay_text_color ) { $colors['overlay_css_classes'][] = 'has-text-color'; } // Give overlay colors priority, fall back to Navigation block colors, then global styles. if ( $has_named_overlay_text_color ) { $colors['overlay_css_classes'][] = sprintf( 'has-%s-color', _wp_to_kebab_case( $context['overlayTextColor'] ) ); } elseif ( $has_picked_overlay_text_color ) { $colors['overlay_inline_styles'] .= sprintf( 'color: %s;', $context['customOverlayTextColor'] ); } // Overlay background colors. $has_named_overlay_background_color = array_key_exists( 'overlayBackgroundColor', $context ); $has_picked_overlay_background_color = array_key_exists( 'customOverlayBackgroundColor', $context ); // If has background color. if ( $has_named_overlay_background_color || $has_picked_overlay_background_color ) { $colors['overlay_css_classes'][] = 'has-background'; } if ( $has_named_overlay_background_color ) { $colors['overlay_css_classes'][] = sprintf( 'has-%s-background-color', _wp_to_kebab_case( $context['overlayBackgroundColor'] ) ); } elseif ( $has_picked_overlay_background_color ) { $colors['overlay_inline_styles'] .= sprintf( 'background-color: %s;', $context['customOverlayBackgroundColor'] ); } return $colors; } /** * Build an array with CSS classes and inline styles defining the font sizes * which will be applied to the pages markup in the front-end when it is a descendant of navigation. * * @since 5.8.0 * * @param array $context Navigation block context. * @return array Font size CSS classes and inline styles. */ function block_core_page_list_build_css_font_sizes( $context ) { // CSS classes. $font_sizes = array( 'css_classes' => array(), 'inline_styles' => '', ); $has_named_font_size = array_key_exists( 'fontSize', $context ); $has_custom_font_size = isset( $context['style']['typography']['fontSize'] ); if ( $has_named_font_size ) { // Add the font size class. $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] ); } elseif ( $has_custom_font_size ) { // Add the custom font size inline style. $font_sizes['inline_styles'] = sprintf( 'font-size: %s;', wp_get_typography_font_size_value( array( 'size' => $context['style']['typography']['fontSize'], ) ) ); } return $font_sizes; } /** * Outputs Page list markup from an array of pages with nested children. * * @since 5.8.0 * * @param boolean $open_submenus_on_click Whether to open submenus on click instead of hover. * @param boolean $show_submenu_icons Whether to show submenu indicator icons. * @param boolean $is_navigation_child If block is a child of Navigation block. * @param array $nested_pages The array of nested pages. * @param boolean $is_nested Whether the submenu is nested or not. * @param array $active_page_ancestor_ids An array of ancestor ids for active page. * @param array $colors Color information for overlay styles. * @param integer $depth The nesting depth. * * @return string List markup. */ function block_core_page_list_render_nested_page_list( $open_submenus_on_click, $show_submenu_icons, $is_navigation_child, $nested_pages, $is_nested, $active_page_ancestor_ids = array(), $colors = array(), $depth = 0 ) { if ( empty( $nested_pages ) ) { return; } $front_page_id = (int) get_option( 'page_on_front' ); $markup = ''; foreach ( (array) $nested_pages as $page ) { $css_class = $page['is_active'] ? ' current-menu-item' : ''; $aria_current = $page['is_active'] ? ' aria-current="page"' : ''; $style_attribute = ''; $css_class .= in_array( $page['page_id'], $active_page_ancestor_ids, true ) ? ' current-menu-ancestor' : ''; if ( isset( $page['children'] ) ) { $css_class .= ' has-child'; } if ( $is_navigation_child ) { $css_class .= ' wp-block-navigation-item'; if ( $open_submenus_on_click ) { $css_class .= ' open-on-click'; } elseif ( $show_submenu_icons ) { $css_class .= ' open-on-hover-click'; } } $navigation_child_content_class = $is_navigation_child ? ' wp-block-navigation-item__content' : ''; // If this is the first level of submenus, include the overlay colors. if ( ( ( 0 < $depth && ! $is_nested ) || $is_nested ) && isset( $colors['overlay_css_classes'], $colors['overlay_inline_styles'] ) ) { $css_class .= ' ' . trim( implode( ' ', $colors['overlay_css_classes'] ) ); if ( '' !== $colors['overlay_inline_styles'] ) { $style_attribute = sprintf( ' style="%s"', esc_attr( $colors['overlay_inline_styles'] ) ); } } if ( (int) $page['page_id'] === $front_page_id ) { $css_class .= ' menu-item-home'; } $title = wp_kses_post( $page['title'] ); $title = $title ? $title : __( '(no title)' ); $aria_label = sprintf( /* translators: Accessibility text. %s: Parent page title. */ __( '%s submenu' ), wp_strip_all_tags( $title ) ); $markup .= '
          • '; if ( isset( $page['children'] ) && $is_navigation_child && $open_submenus_on_click ) { $markup .= ''; } else { $markup .= '' . $title . ''; } if ( isset( $page['children'] ) ) { if ( $is_navigation_child && $show_submenu_icons && ! $open_submenus_on_click ) { $markup .= ''; } $markup .= '
              '; $markup .= block_core_page_list_render_nested_page_list( $open_submenus_on_click, $show_submenu_icons, $is_navigation_child, $page['children'], $is_nested, $active_page_ancestor_ids, $colors, $depth + 1 ); $markup .= '
            '; } $markup .= '
          • '; } return $markup; } /** * Outputs nested array of pages * * @since 5.8.0 * * @param array $current_level The level being iterated through. * @param array $children The children grouped by parent post ID. * * @return array The nested array of pages. */ function block_core_page_list_nest_pages( $current_level, $children ) { if ( empty( $current_level ) ) { return; } foreach ( (array) $current_level as $key => $current ) { if ( isset( $children[ $key ] ) ) { $current_level[ $key ]['children'] = block_core_page_list_nest_pages( $children[ $key ], $children ); } } return $current_level; } /** * Renders the `core/page-list` block on server. * * @since 5.8.0 * * @param array $attributes The block attributes. * @param string $content The saved content. * @param WP_Block $block The parsed block. * * @return string Returns the page list markup. */ function render_block_core_page_list( $attributes, $content, $block ) { static $block_id = 0; ++$block_id; $parent_page_id = $attributes['parentPageID']; $is_nested = $attributes['isNested']; $all_pages = get_pages( array( 'sort_column' => 'menu_order,post_title', 'order' => 'asc', ) ); // If there are no pages, there is nothing to show. if ( empty( $all_pages ) ) { return; } $top_level_pages = array(); $pages_with_children = array(); $active_page_ancestor_ids = array(); foreach ( (array) $all_pages as $page ) { $is_active = ! empty( $page->ID ) && ( get_queried_object_id() === $page->ID ); if ( $is_active ) { $active_page_ancestor_ids = get_post_ancestors( $page->ID ); } if ( $page->post_parent ) { $pages_with_children[ $page->post_parent ][ $page->ID ] = array( 'page_id' => $page->ID, 'title' => $page->post_title, 'link' => get_permalink( $page ), 'is_active' => $is_active, ); } else { $top_level_pages[ $page->ID ] = array( 'page_id' => $page->ID, 'title' => $page->post_title, 'link' => get_permalink( $page ), 'is_active' => $is_active, ); } } $colors = block_core_page_list_build_css_colors( $attributes, $block->context ); $font_sizes = block_core_page_list_build_css_font_sizes( $block->context ); $classes = array_merge( $colors['css_classes'], $font_sizes['css_classes'] ); $style_attribute = ( $colors['inline_styles'] . $font_sizes['inline_styles'] ); $css_classes = trim( implode( ' ', $classes ) ); $nested_pages = block_core_page_list_nest_pages( $top_level_pages, $pages_with_children ); if ( 0 !== $parent_page_id ) { // If the parent page has no child pages, there is nothing to show. if ( ! array_key_exists( $parent_page_id, $pages_with_children ) ) { return; } $nested_pages = block_core_page_list_nest_pages( $pages_with_children[ $parent_page_id ], $pages_with_children ); } $is_navigation_child = array_key_exists( 'showSubmenuIcon', $block->context ); $open_submenus_on_click = array_key_exists( 'openSubmenusOnClick', $block->context ) ? $block->context['openSubmenusOnClick'] : false; $show_submenu_icons = array_key_exists( 'showSubmenuIcon', $block->context ) ? $block->context['showSubmenuIcon'] : false; $wrapper_markup = $is_nested ? '%2$s' : '
              %2$s
            '; $items_markup = block_core_page_list_render_nested_page_list( $open_submenus_on_click, $show_submenu_icons, $is_navigation_child, $nested_pages, $is_nested, $active_page_ancestor_ids, $colors ); $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $css_classes, 'style' => $style_attribute, ) ); return sprintf( $wrapper_markup, $wrapper_attributes, $items_markup ); } /** * Registers the `core/pages` block on server. * * @since 5.8.0 */ function register_block_core_page_list() { register_block_type_from_metadata( __DIR__ . '/page-list', array( 'render_callback' => 'render_block_core_page_list', ) ); } add_action( 'init', 'register_block_core_page_list' ); PKt\Yw w post-excerpt.phpnu[context['postId'] ) ) { return ''; } /* * The purpose of the excerpt length setting is to limit the length of both * automatically generated and user-created excerpts. * Because the excerpt_length filter only applies to auto generated excerpts, * wp_trim_words is used instead. */ $excerpt_length = $attributes['excerptLength']; $excerpt = get_the_excerpt( $block->context['postId'] ); if ( isset( $excerpt_length ) ) { $excerpt = wp_trim_words( $excerpt, $excerpt_length ); } $more_text = ! empty( $attributes['moreText'] ) ? '' . wp_kses_post( $attributes['moreText'] ) . '' : ''; $filter_excerpt_more = static function ( $more ) use ( $more_text ) { return empty( $more_text ) ? $more : ''; }; /** * Some themes might use `excerpt_more` filter to handle the * `more` link displayed after a trimmed excerpt. Since the * block has a `more text` attribute we have to check and * override if needed the return value from this filter. * So if the block's attribute is not empty override the * `excerpt_more` filter and return nothing. This will * result in showing only one `read more` link at a time. */ add_filter( 'excerpt_more', $filter_excerpt_more ); $classes = array(); if ( isset( $attributes['textAlign'] ) ) { $classes[] = 'has-text-align-' . $attributes['textAlign']; } if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { $classes[] = 'has-link-color'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) ); $content = '

            ' . $excerpt; $show_more_on_new_line = ! isset( $attributes['showMoreOnNewLine'] ) || $attributes['showMoreOnNewLine']; if ( $show_more_on_new_line && ! empty( $more_text ) ) { $content .= '

            ' . $more_text . '

            '; } else { $content .= " $more_text

            "; } remove_filter( 'excerpt_more', $filter_excerpt_more ); return sprintf( '
            %2$s
            ', $wrapper_attributes, $content ); } /** * Registers the `core/post-excerpt` block on the server. * * @since 5.8.0 */ function register_block_core_post_excerpt() { register_block_type_from_metadata( __DIR__ . '/post-excerpt', array( 'render_callback' => 'render_block_core_post_excerpt', ) ); } add_action( 'init', 'register_block_core_post_excerpt' ); /** * If themes or plugins filter the excerpt_length, we need to * override the filter in the editor, otherwise * the excerpt length block setting has no effect. * Returns 100 because 100 is the max length in the setting. */ if ( is_admin() || defined( 'REST_REQUEST' ) && REST_REQUEST ) { add_filter( 'excerpt_length', static function () { return 100; }, PHP_INT_MAX ); } PKt\ĄUUcomment-author-name.phpnu[context['commentId'] ) ) { return ''; } $comment = get_comment( $block->context['commentId'] ); $commenter = wp_get_current_commenter(); $show_pending_links = isset( $commenter['comment_author'] ) && $commenter['comment_author']; if ( empty( $comment ) ) { return ''; } $classes = array(); if ( isset( $attributes['textAlign'] ) ) { $classes[] = 'has-text-align-' . $attributes['textAlign']; } if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { $classes[] = 'has-link-color'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) ); $comment_author = get_comment_author( $comment ); $link = get_comment_author_url( $comment ); if ( ! empty( $link ) && ! empty( $attributes['isLink'] ) && ! empty( $attributes['linkTarget'] ) ) { $comment_author = sprintf( '%3s', esc_url( $link ), esc_attr( $attributes['linkTarget'] ), $comment_author ); } if ( '0' === $comment->comment_approved && ! $show_pending_links ) { $comment_author = wp_kses( $comment_author, array() ); } return sprintf( '
            %2$s
            ', $wrapper_attributes, $comment_author ); } /** * Registers the `core/comment-author-name` block on the server. * * @since 6.0.0 */ function register_block_core_comment_author_name() { register_block_type_from_metadata( __DIR__ . '/comment-author-name', array( 'render_callback' => 'render_block_core_comment_author_name', ) ); } add_action( 'init', 'register_block_core_comment_author_name' ); PKt\&c  embed/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/embed", "title": "Embed", "category": "embed", "description": "Add a block that displays content pulled from other sites, like Twitter or YouTube.", "textdomain": "default", "attributes": { "url": { "type": "string", "role": "content" }, "caption": { "type": "rich-text", "source": "rich-text", "selector": "figcaption", "role": "content" }, "type": { "type": "string", "role": "content" }, "providerNameSlug": { "type": "string", "role": "content" }, "allowResponsive": { "type": "boolean", "default": true }, "responsive": { "type": "boolean", "default": false, "role": "content" }, "previewable": { "type": "boolean", "default": true, "role": "content" } }, "supports": { "align": true, "spacing": { "margin": true }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-embed-editor", "style": "wp-block-embed" } PKt\{96embed/editor.min.cssnu[.wp-block-embed{clear:both;margin-left:0;margin-right:0}.wp-block-embed.is-loading{display:flex;justify-content:center}.wp-block-embed .wp-block-embed__placeholder-input{flex:1 1 auto}.wp-block-embed .components-placeholder__error{word-break:break-word}.wp-block-post-content .wp-block-embed__learn-more a{color:var(--wp-admin-theme-color)}.block-library-embed__interactive-overlay{bottom:0;left:0;opacity:0;position:absolute;right:0;top:0}.wp-block[data-align=left]>.wp-block-embed,.wp-block[data-align=right]>.wp-block-embed{max-width:360px;width:100%}.wp-block[data-align=left]>.wp-block-embed .wp-block-embed__wrapper,.wp-block[data-align=right]>.wp-block-embed .wp-block-embed__wrapper{min-width:280px}PKt\<ţembed/theme.min.cssnu[.wp-block-embed :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-embed :where(figcaption){color:#ffffffa6}.wp-block-embed{margin:0 0 1em}PKt\kembed/theme.cssnu[.wp-block-embed :where(figcaption){ color:#555; font-size:13px; text-align:center; } .is-dark-theme .wp-block-embed :where(figcaption){ color:#ffffffa6; } .wp-block-embed{ margin:0 0 1em; }PKt\t<<embed/style.min.cssnu[.wp-block-embed.alignleft,.wp-block-embed.alignright,.wp-block[data-align=left]>[data-type="core/embed"],.wp-block[data-align=right]>[data-type="core/embed"]{max-width:360px;width:100%}.wp-block-embed.alignleft .wp-block-embed__wrapper,.wp-block-embed.alignright .wp-block-embed__wrapper,.wp-block[data-align=left]>[data-type="core/embed"] .wp-block-embed__wrapper,.wp-block[data-align=right]>[data-type="core/embed"] .wp-block-embed__wrapper{min-width:280px}.wp-block-cover .wp-block-embed{min-height:240px;min-width:320px}.wp-block-embed{overflow-wrap:break-word}.wp-block-embed :where(figcaption){margin-bottom:1em;margin-top:.5em}.wp-block-embed iframe{max-width:100%}.wp-block-embed__wrapper{position:relative}.wp-embed-responsive .wp-has-aspect-ratio .wp-block-embed__wrapper:before{content:"";display:block;padding-top:50%}.wp-embed-responsive .wp-has-aspect-ratio iframe{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}.wp-embed-responsive .wp-embed-aspect-21-9 .wp-block-embed__wrapper:before{padding-top:42.85%}.wp-embed-responsive .wp-embed-aspect-18-9 .wp-block-embed__wrapper:before{padding-top:50%}.wp-embed-responsive .wp-embed-aspect-16-9 .wp-block-embed__wrapper:before{padding-top:56.25%}.wp-embed-responsive .wp-embed-aspect-4-3 .wp-block-embed__wrapper:before{padding-top:75%}.wp-embed-responsive .wp-embed-aspect-1-1 .wp-block-embed__wrapper:before{padding-top:100%}.wp-embed-responsive .wp-embed-aspect-9-16 .wp-block-embed__wrapper:before{padding-top:177.77%}.wp-embed-responsive .wp-embed-aspect-1-2 .wp-block-embed__wrapper:before{padding-top:200%}PKt\$Fembed/style-rtl.cssnu[.wp-block-embed.alignleft,.wp-block-embed.alignright,.wp-block[data-align=left]>[data-type="core/embed"],.wp-block[data-align=right]>[data-type="core/embed"]{ max-width:360px; width:100%; } .wp-block-embed.alignleft .wp-block-embed__wrapper,.wp-block-embed.alignright .wp-block-embed__wrapper,.wp-block[data-align=left]>[data-type="core/embed"] .wp-block-embed__wrapper,.wp-block[data-align=right]>[data-type="core/embed"] .wp-block-embed__wrapper{ min-width:280px; } .wp-block-cover .wp-block-embed{ min-height:240px; min-width:320px; } .wp-block-embed{ overflow-wrap:break-word; } .wp-block-embed :where(figcaption){ margin-bottom:1em; margin-top:.5em; } .wp-block-embed iframe{ max-width:100%; } .wp-block-embed__wrapper{ position:relative; } .wp-embed-responsive .wp-has-aspect-ratio .wp-block-embed__wrapper:before{ content:""; display:block; padding-top:50%; } .wp-embed-responsive .wp-has-aspect-ratio iframe{ bottom:0; height:100%; left:0; position:absolute; right:0; top:0; width:100%; } .wp-embed-responsive .wp-embed-aspect-21-9 .wp-block-embed__wrapper:before{ padding-top:42.85%; } .wp-embed-responsive .wp-embed-aspect-18-9 .wp-block-embed__wrapper:before{ padding-top:50%; } .wp-embed-responsive .wp-embed-aspect-16-9 .wp-block-embed__wrapper:before{ padding-top:56.25%; } .wp-embed-responsive .wp-embed-aspect-4-3 .wp-block-embed__wrapper:before{ padding-top:75%; } .wp-embed-responsive .wp-embed-aspect-1-1 .wp-block-embed__wrapper:before{ padding-top:100%; } .wp-embed-responsive .wp-embed-aspect-9-16 .wp-block-embed__wrapper:before{ padding-top:177.77%; } .wp-embed-responsive .wp-embed-aspect-1-2 .wp-block-embed__wrapper:before{ padding-top:200%; }PKt\f=embed/editor-rtl.cssnu[.wp-block-embed{ clear:both; margin-left:0; margin-right:0; } .wp-block-embed.is-loading{ display:flex; justify-content:center; } .wp-block-embed .wp-block-embed__placeholder-input{ flex:1 1 auto; } .wp-block-embed .components-placeholder__error{ word-break:break-word; } .wp-block-post-content .wp-block-embed__learn-more a{ color:var(--wp-admin-theme-color); } .block-library-embed__interactive-overlay{ bottom:0; left:0; opacity:0; position:absolute; right:0; top:0; } .wp-block[data-align=left]>.wp-block-embed,.wp-block[data-align=right]>.wp-block-embed{ max-width:360px; width:100%; } .wp-block[data-align=left]>.wp-block-embed .wp-block-embed__wrapper,.wp-block[data-align=right]>.wp-block-embed .wp-block-embed__wrapper{ min-width:280px; }PKt\kembed/theme-rtl.cssnu[.wp-block-embed :where(figcaption){ color:#555; font-size:13px; text-align:center; } .is-dark-theme .wp-block-embed :where(figcaption){ color:#ffffffa6; } .wp-block-embed{ margin:0 0 1em; }PKt\$Fembed/style.cssnu[.wp-block-embed.alignleft,.wp-block-embed.alignright,.wp-block[data-align=left]>[data-type="core/embed"],.wp-block[data-align=right]>[data-type="core/embed"]{ max-width:360px; width:100%; } .wp-block-embed.alignleft .wp-block-embed__wrapper,.wp-block-embed.alignright .wp-block-embed__wrapper,.wp-block[data-align=left]>[data-type="core/embed"] .wp-block-embed__wrapper,.wp-block[data-align=right]>[data-type="core/embed"] .wp-block-embed__wrapper{ min-width:280px; } .wp-block-cover .wp-block-embed{ min-height:240px; min-width:320px; } .wp-block-embed{ overflow-wrap:break-word; } .wp-block-embed :where(figcaption){ margin-bottom:1em; margin-top:.5em; } .wp-block-embed iframe{ max-width:100%; } .wp-block-embed__wrapper{ position:relative; } .wp-embed-responsive .wp-has-aspect-ratio .wp-block-embed__wrapper:before{ content:""; display:block; padding-top:50%; } .wp-embed-responsive .wp-has-aspect-ratio iframe{ bottom:0; height:100%; left:0; position:absolute; right:0; top:0; width:100%; } .wp-embed-responsive .wp-embed-aspect-21-9 .wp-block-embed__wrapper:before{ padding-top:42.85%; } .wp-embed-responsive .wp-embed-aspect-18-9 .wp-block-embed__wrapper:before{ padding-top:50%; } .wp-embed-responsive .wp-embed-aspect-16-9 .wp-block-embed__wrapper:before{ padding-top:56.25%; } .wp-embed-responsive .wp-embed-aspect-4-3 .wp-block-embed__wrapper:before{ padding-top:75%; } .wp-embed-responsive .wp-embed-aspect-1-1 .wp-block-embed__wrapper:before{ padding-top:100%; } .wp-embed-responsive .wp-embed-aspect-9-16 .wp-block-embed__wrapper:before{ padding-top:177.77%; } .wp-embed-responsive .wp-embed-aspect-1-2 .wp-block-embed__wrapper:before{ padding-top:200%; }PKt\<ţembed/theme-rtl.min.cssnu[.wp-block-embed :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-embed :where(figcaption){color:#ffffffa6}.wp-block-embed{margin:0 0 1em}PKt\t<<embed/style-rtl.min.cssnu[.wp-block-embed.alignleft,.wp-block-embed.alignright,.wp-block[data-align=left]>[data-type="core/embed"],.wp-block[data-align=right]>[data-type="core/embed"]{max-width:360px;width:100%}.wp-block-embed.alignleft .wp-block-embed__wrapper,.wp-block-embed.alignright .wp-block-embed__wrapper,.wp-block[data-align=left]>[data-type="core/embed"] .wp-block-embed__wrapper,.wp-block[data-align=right]>[data-type="core/embed"] .wp-block-embed__wrapper{min-width:280px}.wp-block-cover .wp-block-embed{min-height:240px;min-width:320px}.wp-block-embed{overflow-wrap:break-word}.wp-block-embed :where(figcaption){margin-bottom:1em;margin-top:.5em}.wp-block-embed iframe{max-width:100%}.wp-block-embed__wrapper{position:relative}.wp-embed-responsive .wp-has-aspect-ratio .wp-block-embed__wrapper:before{content:"";display:block;padding-top:50%}.wp-embed-responsive .wp-has-aspect-ratio iframe{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}.wp-embed-responsive .wp-embed-aspect-21-9 .wp-block-embed__wrapper:before{padding-top:42.85%}.wp-embed-responsive .wp-embed-aspect-18-9 .wp-block-embed__wrapper:before{padding-top:50%}.wp-embed-responsive .wp-embed-aspect-16-9 .wp-block-embed__wrapper:before{padding-top:56.25%}.wp-embed-responsive .wp-embed-aspect-4-3 .wp-block-embed__wrapper:before{padding-top:75%}.wp-embed-responsive .wp-embed-aspect-1-1 .wp-block-embed__wrapper:before{padding-top:100%}.wp-embed-responsive .wp-embed-aspect-9-16 .wp-block-embed__wrapper:before{padding-top:177.77%}.wp-embed-responsive .wp-embed-aspect-1-2 .wp-block-embed__wrapper:before{padding-top:200%}PKt\{96embed/editor-rtl.min.cssnu[.wp-block-embed{clear:both;margin-left:0;margin-right:0}.wp-block-embed.is-loading{display:flex;justify-content:center}.wp-block-embed .wp-block-embed__placeholder-input{flex:1 1 auto}.wp-block-embed .components-placeholder__error{word-break:break-word}.wp-block-post-content .wp-block-embed__learn-more a{color:var(--wp-admin-theme-color)}.block-library-embed__interactive-overlay{bottom:0;left:0;opacity:0;position:absolute;right:0;top:0}.wp-block[data-align=left]>.wp-block-embed,.wp-block[data-align=right]>.wp-block-embed{max-width:360px;width:100%}.wp-block[data-align=left]>.wp-block-embed .wp-block-embed__wrapper,.wp-block[data-align=right]>.wp-block-embed .wp-block-embed__wrapper{min-width:280px}PKt\f=embed/editor.cssnu[.wp-block-embed{ clear:both; margin-left:0; margin-right:0; } .wp-block-embed.is-loading{ display:flex; justify-content:center; } .wp-block-embed .wp-block-embed__placeholder-input{ flex:1 1 auto; } .wp-block-embed .components-placeholder__error{ word-break:break-word; } .wp-block-post-content .wp-block-embed__learn-more a{ color:var(--wp-admin-theme-color); } .block-library-embed__interactive-overlay{ bottom:0; left:0; opacity:0; position:absolute; right:0; top:0; } .wp-block[data-align=left]>.wp-block-embed,.wp-block[data-align=right]>.wp-block-embed{ max-width:360px; width:100%; } .wp-block[data-align=left]>.wp-block-embed .wp-block-embed__wrapper,.wp-block[data-align=right]>.wp-block-embed .wp-block-embed__wrapper{ min-width:280px; }PKt\$rqq avatar.phpnu[context['commentId'] ) ) { if ( isset( $attributes['userId'] ) ) { $author_id = $attributes['userId']; } elseif ( isset( $block->context['postId'] ) ) { $author_id = get_post_field( 'post_author', $block->context['postId'] ); } else { $author_id = get_query_var( 'author' ); } if ( empty( $author_id ) ) { return ''; } $author_name = get_the_author_meta( 'display_name', $author_id ); // translators: %s: Author name. $alt = sprintf( __( '%s Avatar' ), $author_name ); $avatar_block = get_avatar( $author_id, $size, '', $alt, array( 'extra_attr' => $image_styles, 'class' => $image_classes, ) ); if ( isset( $attributes['isLink'] ) && $attributes['isLink'] ) { $label = ''; if ( '_blank' === $attributes['linkTarget'] ) { // translators: %s is the Author name. $label = 'aria-label="' . esc_attr( sprintf( __( '(%s author archive, opens in a new tab)' ), $author_name ) ) . '"'; } // translators: 1: Author archive link. 2: Link target. %3$s Aria label. %4$s Avatar image. $avatar_block = sprintf( '%4$s', esc_url( get_author_posts_url( $author_id ) ), esc_attr( $attributes['linkTarget'] ), $label, $avatar_block ); } return sprintf( '
            %2s
            ', $wrapper_attributes, $avatar_block ); } $comment = get_comment( $block->context['commentId'] ); if ( ! $comment ) { return ''; } /* translators: %s: Author name. */ $alt = sprintf( __( '%s Avatar' ), $comment->comment_author ); $avatar_block = get_avatar( $comment, $size, '', $alt, array( 'extra_attr' => $image_styles, 'class' => $image_classes, ) ); if ( isset( $attributes['isLink'] ) && $attributes['isLink'] && isset( $comment->comment_author_url ) && '' !== $comment->comment_author_url ) { $label = ''; if ( '_blank' === $attributes['linkTarget'] ) { // translators: %s: Comment author name. $label = 'aria-label="' . esc_attr( sprintf( __( '(%s website link, opens in a new tab)' ), $comment->comment_author ) ) . '"'; } $avatar_block = sprintf( '%4$s', esc_url( $comment->comment_author_url ), esc_attr( $attributes['linkTarget'] ), $label, $avatar_block ); } return sprintf( '
            %2s
            ', $wrapper_attributes, $avatar_block ); } /** * Generates class names and styles to apply the border support styles for * the Avatar block. * * @since 6.3.0 * * @param array $attributes The block attributes. * @return array The border-related classnames and styles for the block. */ function get_block_core_avatar_border_attributes( $attributes ) { $border_styles = array(); $sides = array( 'top', 'right', 'bottom', 'left' ); // Border radius. if ( isset( $attributes['style']['border']['radius'] ) ) { $border_styles['radius'] = $attributes['style']['border']['radius']; } // Border style. if ( isset( $attributes['style']['border']['style'] ) ) { $border_styles['style'] = $attributes['style']['border']['style']; } // Border width. if ( isset( $attributes['style']['border']['width'] ) ) { $border_styles['width'] = $attributes['style']['border']['width']; } // Border color. $preset_color = array_key_exists( 'borderColor', $attributes ) ? "var:preset|color|{$attributes['borderColor']}" : null; $custom_color = $attributes['style']['border']['color'] ?? null; $border_styles['color'] = $preset_color ? $preset_color : $custom_color; // Individual border styles e.g. top, left etc. foreach ( $sides as $side ) { $border = $attributes['style']['border'][ $side ] ?? null; $border_styles[ $side ] = array( 'color' => isset( $border['color'] ) ? $border['color'] : null, 'style' => isset( $border['style'] ) ? $border['style'] : null, 'width' => isset( $border['width'] ) ? $border['width'] : null, ); } $styles = wp_style_engine_get_styles( array( 'border' => $border_styles ) ); $attributes = array(); if ( ! empty( $styles['classnames'] ) ) { $attributes['class'] = $styles['classnames']; } if ( ! empty( $styles['css'] ) ) { $attributes['style'] = $styles['css']; } return $attributes; } /** * Registers the `core/avatar` block on the server. * * @since 6.0.0 */ function register_block_core_avatar() { register_block_type_from_metadata( __DIR__ . '/avatar', array( 'render_callback' => 'render_block_core_avatar', ) ); } add_action( 'init', 'register_block_core_avatar' ); PKt\&sDDpost-content/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/post-content", "title": "Content", "category": "theme", "description": "Displays the contents of a post or page.", "textdomain": "default", "usesContext": [ "postId", "postType", "queryId" ], "example": { "viewportWidth": 350 }, "supports": { "align": [ "wide", "full" ], "html": false, "layout": true, "background": { "backgroundImage": true, "backgroundSize": true, "__experimentalDefaultControls": { "backgroundImage": true } }, "dimensions": { "minHeight": true }, "spacing": { "blockGap": true, "padding": true, "margin": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "color": { "gradients": true, "heading": true, "link": true, "__experimentalDefaultControls": { "background": false, "text": false } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "style": "wp-block-post-content", "editorStyle": "wp-block-post-content-editor" } PKt\O))post-content/style.min.cssnu[.wp-block-post-content{display:flow-root}PKt\h՟..post-content/style-rtl.cssnu[.wp-block-post-content{ display:flow-root; }PKt\h՟..post-content/style.cssnu[.wp-block-post-content{ display:flow-root; }PKt\O))post-content/style-rtl.min.cssnu[.wp-block-post-content{display:flow-root}PKt\Qcomment-reply-link.phpnu[context['commentId'] ) ) { return ''; } $thread_comments = get_option( 'thread_comments' ); if ( ! $thread_comments ) { return ''; } $comment = get_comment( $block->context['commentId'] ); if ( empty( $comment ) ) { return ''; } $depth = 1; $max_depth = get_option( 'thread_comments_depth' ); $parent_id = $comment->comment_parent; // Compute comment's depth iterating over its ancestors. while ( ! empty( $parent_id ) ) { ++$depth; $parent_id = get_comment( $parent_id )->comment_parent; } $comment_reply_link = get_comment_reply_link( array( 'depth' => $depth, 'max_depth' => $max_depth, ), $comment ); // Render nothing if the generated reply link is empty. if ( empty( $comment_reply_link ) ) { return; } $classes = array(); if ( isset( $attributes['textAlign'] ) ) { $classes[] = 'has-text-align-' . $attributes['textAlign']; } if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { $classes[] = 'has-link-color'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) ); return sprintf( '
            %2$s
            ', $wrapper_attributes, $comment_reply_link ); } /** * Registers the `core/comment-reply-link` block on the server. * * @since 6.0.0 */ function register_block_core_comment_reply_link() { register_block_type_from_metadata( __DIR__ . '/comment-reply-link', array( 'render_callback' => 'render_block_core_comment_reply_link', ) ); } add_action( 'init', 'register_block_core_comment_reply_link' ); PKt\: index.phpnu[ 'style', 'editorStyle' => 'editor', ); static $core_blocks_meta; if ( ! $core_blocks_meta ) { $core_blocks_meta = require BLOCKS_PATH . 'blocks-json.php'; } $files = false; $transient_name = 'wp_core_block_css_files'; /* * Ignore transient cache when the development mode is set to 'core'. Why? To avoid interfering with * the core developer's workflow. */ $can_use_cached = ! wp_is_development_mode( 'core' ); if ( $can_use_cached ) { $cached_files = get_transient( $transient_name ); // Check the validity of cached values by checking against the current WordPress version. if ( is_array( $cached_files ) && isset( $cached_files['version'] ) && $cached_files['version'] === $wp_version && isset( $cached_files['files'] ) ) { $files = $cached_files['files']; } } if ( ! $files ) { $files = glob( wp_normalize_path( BLOCKS_PATH . '**/**.css' ) ); // Normalize BLOCKS_PATH prior to substitution for Windows environments. $normalized_blocks_path = wp_normalize_path( BLOCKS_PATH ); $files = array_map( static function ( $file ) use ( $normalized_blocks_path ) { return str_replace( $normalized_blocks_path, '', $file ); }, $files ); // Save core block style paths in cache when not in development mode. if ( $can_use_cached ) { set_transient( $transient_name, array( 'version' => $wp_version, 'files' => $files, ) ); } } $register_style = static function ( $name, $filename, $style_handle ) use ( $blocks_url, $suffix, $wp_styles, $files ) { $style_path = "{$name}/{$filename}{$suffix}.css"; $path = wp_normalize_path( BLOCKS_PATH . $style_path ); if ( ! in_array( $style_path, $files, true ) ) { $wp_styles->add( $style_handle, false ); return; } $wp_styles->add( $style_handle, $blocks_url . $style_path ); $wp_styles->add_data( $style_handle, 'path', $path ); $rtl_file = "{$name}/{$filename}-rtl{$suffix}.css"; if ( is_rtl() && in_array( $rtl_file, $files, true ) ) { $wp_styles->add_data( $style_handle, 'rtl', 'replace' ); $wp_styles->add_data( $style_handle, 'suffix', $suffix ); $wp_styles->add_data( $style_handle, 'path', str_replace( "{$suffix}.css", "-rtl{$suffix}.css", $path ) ); } }; foreach ( $core_blocks_meta as $name => $schema ) { /** This filter is documented in wp-includes/blocks.php */ $schema = apply_filters( 'block_type_metadata', $schema ); // Backfill these properties similar to `register_block_type_from_metadata()`. if ( ! isset( $schema['style'] ) ) { $schema['style'] = "wp-block-{$name}"; } if ( ! isset( $schema['editorStyle'] ) ) { $schema['editorStyle'] = "wp-block-{$name}-editor"; } // Register block theme styles. $register_style( $name, 'theme', "wp-block-{$name}-theme" ); foreach ( $style_fields as $style_field => $filename ) { $style_handle = $schema[ $style_field ]; if ( is_array( $style_handle ) ) { continue; } $register_style( $name, $filename, $style_handle ); } } } add_action( 'init', 'register_core_block_style_handles', 9 ); /** * Registers core block types using metadata files. * Dynamic core blocks are registered separately. * * @since 5.5.0 */ function register_core_block_types_from_metadata() { $block_folders = require BLOCKS_PATH . 'require-static-blocks.php'; foreach ( $block_folders as $block_folder ) { register_block_type_from_metadata( BLOCKS_PATH . $block_folder ); } } add_action( 'init', 'register_core_block_types_from_metadata' ); /** * Registers the core block metadata collection. * * This function is hooked into the 'init' action with a priority of 9, * ensuring that the core block metadata is registered before the regular * block initialization that happens at priority 10. * * @since 6.7.0 */ function wp_register_core_block_metadata_collection() { wp_register_block_metadata_collection( BLOCKS_PATH, BLOCKS_PATH . 'blocks-json.php' ); } add_action( 'init', 'wp_register_core_block_metadata_collection', 9 ); PKt\Y,,legacy-widget/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/legacy-widget", "title": "Legacy Widget", "category": "widgets", "description": "Display a legacy widget.", "textdomain": "default", "attributes": { "id": { "type": "string", "default": null }, "idBase": { "type": "string", "default": null }, "instance": { "type": "object", "default": null } }, "supports": { "html": false, "customClassName": false, "reusable": false }, "editorStyle": "wp-block-legacy-widget-editor" } PKt\jLQ66query-total/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/query-total", "title": "Query Total", "category": "theme", "ancestor": [ "core/query" ], "description": "Display the total number of results in a query.", "textdomain": "default", "attributes": { "displayType": { "type": "string", "default": "total-results" } }, "usesContext": [ "queryId", "query" ], "supports": { "align": [ "wide", "full" ], "html": false, "spacing": { "margin": true, "padding": true }, "color": { "gradients": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } }, "interactivity": { "clientNavigation": true } }, "style": "wp-block-query-total" } PKt\.c,,query-total/style.min.cssnu[.wp-block-query-total{box-sizing:border-box}PKt\T11query-total/style-rtl.cssnu[.wp-block-query-total{ box-sizing:border-box; }PKt\T11query-total/style.cssnu[.wp-block-query-total{ box-sizing:border-box; }PKt\.c,,query-total/style-rtl.min.cssnu[.wp-block-query-total{box-sizing:border-box}PKt\heading/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/heading", "title": "Heading", "category": "text", "description": "Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content.", "keywords": [ "title", "subtitle" ], "textdomain": "default", "attributes": { "textAlign": { "type": "string" }, "content": { "type": "rich-text", "source": "rich-text", "selector": "h1,h2,h3,h4,h5,h6", "role": "content" }, "level": { "type": "number", "default": 2 }, "levelOptions": { "type": "array" }, "placeholder": { "type": "string" } }, "supports": { "align": [ "wide", "full" ], "anchor": true, "className": true, "splitting": true, "__experimentalBorder": { "color": true, "radius": true, "style": true, "width": true }, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontStyle": true, "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalWritingMode": true, "__experimentalDefaultControls": { "fontSize": true } }, "__unstablePasteTextInline": true, "__experimentalSlashInserter": true, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-heading-editor", "style": "wp-block-heading" } PKt\`.heading/style.min.cssnu[h1.has-background,h2.has-background,h3.has-background,h4.has-background,h5.has-background,h6.has-background{padding:1.25em 2.375em}h1.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h1.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h2.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h2.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h3.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h3.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h4.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h4.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h5.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h5.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h6.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h6.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]){rotate:180deg}PKt\Reheading/style-rtl.cssnu[h1.has-background,h2.has-background,h3.has-background,h4.has-background,h5.has-background,h6.has-background{ padding:1.25em 2.375em; } h1.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h1.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h2.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h2.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h3.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h3.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h4.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h4.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h5.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h5.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h6.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h6.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]){ rotate:180deg; }PKt\Reheading/style.cssnu[h1.has-background,h2.has-background,h3.has-background,h4.has-background,h5.has-background,h6.has-background{ padding:1.25em 2.375em; } h1.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h1.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h2.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h2.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h3.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h3.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h4.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h4.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h5.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h5.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h6.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h6.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]){ rotate:180deg; }PKt\`.heading/style-rtl.min.cssnu[h1.has-background,h2.has-background,h3.has-background,h4.has-background,h5.has-background,h6.has-background{padding:1.25em 2.375em}h1.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h1.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h2.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h2.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h3.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h3.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h4.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h4.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h5.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h5.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h6.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h6.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]){rotate:180deg}PKt\#post-author-name/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/post-author-name", "title": "Author Name", "category": "theme", "description": "The author name.", "textdomain": "default", "attributes": { "textAlign": { "type": "string" }, "isLink": { "type": "boolean", "default": false, "role": "content" }, "linkTarget": { "type": "string", "default": "_self", "role": "content" } }, "usesContext": [ "postType", "postId" ], "example": { "viewportWidth": 350 }, "supports": { "html": false, "spacing": { "margin": true, "padding": true }, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true, "link": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "style": "wp-block-post-author-name" } PKt\ A11post-author-name/style.min.cssnu[.wp-block-post-author-name{box-sizing:border-box}PKt\0e66post-author-name/style-rtl.cssnu[.wp-block-post-author-name{ box-sizing:border-box; }PKt\0e66post-author-name/style.cssnu[.wp-block-post-author-name{ box-sizing:border-box; }PKt\ A11"post-author-name/style-rtl.min.cssnu[.wp-block-post-author-name{box-sizing:border-box}PKt\E7comment-edit-link.phpnu[context['commentId'] ) || ! current_user_can( 'edit_comment', $block->context['commentId'] ) ) { return ''; } $edit_comment_link = get_edit_comment_link( $block->context['commentId'] ); $link_atts = ''; if ( ! empty( $attributes['linkTarget'] ) ) { $link_atts .= sprintf( 'target="%s"', esc_attr( $attributes['linkTarget'] ) ); } $classes = array(); if ( isset( $attributes['textAlign'] ) ) { $classes[] = 'has-text-align-' . $attributes['textAlign']; } if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { $classes[] = 'has-link-color'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) ); return sprintf( '', $wrapper_attributes, esc_url( $edit_comment_link ), $link_atts, esc_html__( 'Edit' ) ); } /** * Registers the `core/comment-edit-link` block on the server. * * @since 6.0.0 */ function register_block_core_comment_edit_link() { register_block_type_from_metadata( __DIR__ . '/comment-edit-link', array( 'render_callback' => 'render_block_core_comment_edit_link', ) ); } add_action( 'init', 'register_block_core_comment_edit_link' ); PKt\E#Ȅfootnotes/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/footnotes", "title": "Footnotes", "category": "text", "description": "Display footnotes added to the page.", "keywords": [ "references" ], "textdomain": "default", "usesContext": [ "postId", "postType" ], "supports": { "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": false, "color": false, "width": false, "style": false } }, "color": { "background": true, "link": true, "text": true, "__experimentalDefaultControls": { "link": true, "text": true } }, "html": false, "multiple": false, "reusable": false, "inserter": false, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalTextDecoration": true, "__experimentalFontStyle": true, "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, "__experimentalTextTransform": true, "__experimentalWritingMode": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } }, "style": "wp-block-footnotes" } PKt\GtYOfootnotes/style.min.cssnu[.editor-styles-wrapper,.entry-content{counter-reset:footnotes}a[data-fn].fn{counter-increment:footnotes;display:inline-flex;font-size:smaller;text-decoration:none;text-indent:-9999999px;vertical-align:super}a[data-fn].fn:after{content:"[" counter(footnotes) "]";float:left;text-indent:0}PKt\ 2HHfootnotes/style-rtl.cssnu[.editor-styles-wrapper,.entry-content{ counter-reset:footnotes; } a[data-fn].fn{ counter-increment:footnotes; display:inline-flex; font-size:smaller; text-decoration:none; text-indent:-9999999px; vertical-align:super; } a[data-fn].fn:after{ content:"[" counter(footnotes) "]"; float:right; text-indent:0; }PKt\1"zGGfootnotes/style.cssnu[.editor-styles-wrapper,.entry-content{ counter-reset:footnotes; } a[data-fn].fn{ counter-increment:footnotes; display:inline-flex; font-size:smaller; text-decoration:none; text-indent:-9999999px; vertical-align:super; } a[data-fn].fn:after{ content:"[" counter(footnotes) "]"; float:left; text-indent:0; }PKt\}  footnotes/style-rtl.min.cssnu[.editor-styles-wrapper,.entry-content{counter-reset:footnotes}a[data-fn].fn{counter-increment:footnotes;display:inline-flex;font-size:smaller;text-decoration:none;text-indent:-9999999px;vertical-align:super}a[data-fn].fn:after{content:"[" counter(footnotes) "]";float:right;text-indent:0}PKt\x8}#list-item/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/list-item", "title": "List Item", "category": "text", "parent": [ "core/list" ], "allowedBlocks": [ "core/list" ], "description": "An individual item within a list.", "textdomain": "default", "attributes": { "placeholder": { "type": "string" }, "content": { "type": "rich-text", "source": "rich-text", "selector": "li", "role": "content" } }, "supports": { "anchor": true, "className": false, "splitting": true, "__experimentalBorder": { "color": true, "radius": true, "style": true, "width": true }, "color": { "gradients": true, "link": true, "background": true, "__experimentalDefaultControls": { "text": true } }, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } }, "selectors": { "root": ".wp-block-list > li", "border": ".wp-block-list:not(.wp-block-list .wp-block-list) > li" } } PKt\w?navigation.phpnu[ wrapper. * * @since 6.5.0 * * @var array */ private static $needs_list_item_wrapper = array( 'core/site-title', 'core/site-logo', 'core/social-links', ); /** * Keeps track of all the navigation names that have been seen. * * @since 6.5.0 * * @var array */ private static $seen_menu_names = array(); /** * Returns whether or not this is responsive navigation. * * @since 6.5.0 * * @param array $attributes The block attributes. * @return bool Returns whether or not this is responsive navigation. */ private static function is_responsive( $attributes ) { /** * This is for backwards compatibility after the `isResponsive` attribute was been removed. */ $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive']; return isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute; } /** * Returns whether or not a navigation has a submenu. * * @since 6.5.0 * * @param WP_Block_List $inner_blocks The list of inner blocks. * @return bool Returns whether or not a navigation has a submenu and also sets the member variable. */ private static function has_submenus( $inner_blocks ) { if ( true === static::$has_submenus ) { return static::$has_submenus; } foreach ( $inner_blocks as $inner_block ) { // If this is a page list then work out if any of the pages have children. if ( 'core/page-list' === $inner_block->name ) { $all_pages = get_pages( array( 'sort_column' => 'menu_order,post_title', 'order' => 'asc', ) ); foreach ( (array) $all_pages as $page ) { if ( $page->post_parent ) { static::$has_submenus = true; break; } } } // If this is a navigation submenu then we know we have submenus. if ( 'core/navigation-submenu' === $inner_block->name ) { static::$has_submenus = true; break; } } return static::$has_submenus; } /** * Determine whether the navigation blocks is interactive. * * @since 6.5.0 * * @param array $attributes The block attributes. * @param WP_Block_List $inner_blocks The list of inner blocks. * @return bool Returns whether or not to load the view script. */ private static function is_interactive( $attributes, $inner_blocks ) { $has_submenus = static::has_submenus( $inner_blocks ); $is_responsive_menu = static::is_responsive( $attributes ); return ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ) || $is_responsive_menu; } /** * Returns whether or not a block needs a list item wrapper. * * @since 6.5.0 * * @param WP_Block $block The block. * @return bool Returns whether or not a block needs a list item wrapper. */ private static function does_block_need_a_list_item_wrapper( $block ) { /** * Filter the list of blocks that need a list item wrapper. * * Affords the ability to customize which blocks need a list item wrapper when rendered * within a core/navigation block. * This is useful for blocks that are not list items but should be wrapped in a list * item when used as a child of a navigation block. * * @since 6.5.0 * * @param array $needs_list_item_wrapper The list of blocks that need a list item wrapper. * @return array The list of blocks that need a list item wrapper. */ $needs_list_item_wrapper = apply_filters( 'block_core_navigation_listable_blocks', static::$needs_list_item_wrapper ); return in_array( $block->name, $needs_list_item_wrapper, true ); } /** * Returns the markup for a single inner block. * * @since 6.5.0 * * @param WP_Block $inner_block The inner block. * @return string Returns the markup for a single inner block. */ private static function get_markup_for_inner_block( $inner_block ) { $inner_block_content = $inner_block->render(); if ( ! empty( $inner_block_content ) ) { if ( static::does_block_need_a_list_item_wrapper( $inner_block ) ) { return '
          • ' . $inner_block_content . '
          • '; } } return $inner_block_content; } /** * Returns the html for the inner blocks of the navigation block. * * @since 6.5.0 * * @param array $attributes The block attributes. * @param WP_Block_List $inner_blocks The list of inner blocks. * @return string Returns the html for the inner blocks of the navigation block. */ private static function get_inner_blocks_html( $attributes, $inner_blocks ) { $has_submenus = static::has_submenus( $inner_blocks ); $is_interactive = static::is_interactive( $attributes, $inner_blocks ); $style = static::get_styles( $attributes ); $class = static::get_classes( $attributes ); $container_attributes = get_block_wrapper_attributes( array( 'class' => 'wp-block-navigation__container ' . $class, 'style' => $style, ) ); $inner_blocks_html = ''; $is_list_open = false; foreach ( $inner_blocks as $inner_block ) { $inner_block_markup = static::get_markup_for_inner_block( $inner_block ); $p = new WP_HTML_Tag_Processor( $inner_block_markup ); $is_list_item = $p->next_tag( 'LI' ); if ( $is_list_item && ! $is_list_open ) { $is_list_open = true; $inner_blocks_html .= sprintf( '
              ', $container_attributes ); } if ( ! $is_list_item && $is_list_open ) { $is_list_open = false; $inner_blocks_html .= '
            '; } $inner_blocks_html .= $inner_block_markup; } if ( $is_list_open ) { $inner_blocks_html .= '
          '; } // Add directives to the submenu if needed. if ( $has_submenus && $is_interactive ) { $tags = new WP_HTML_Tag_Processor( $inner_blocks_html ); $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $tags, $attributes ); } return $inner_blocks_html; } /** * Gets the inner blocks for the navigation block from the navigation post. * * @since 6.5.0 * * @param array $attributes The block attributes. * @return WP_Block_List Returns the inner blocks for the navigation block. */ private static function get_inner_blocks_from_navigation_post( $attributes ) { $navigation_post = get_post( $attributes['ref'] ); if ( ! isset( $navigation_post ) ) { return new WP_Block_List( array(), $attributes ); } // Only published posts are valid. If this is changed then a corresponding change // must also be implemented in `use-navigation-menu.js`. if ( 'publish' === $navigation_post->post_status ) { $parsed_blocks = parse_blocks( $navigation_post->post_content ); // 'parse_blocks' includes a null block with '\n\n' as the content when // it encounters whitespace. This code strips it. $blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks ); // Re-serialize, and run Block Hooks algorithm to inject hooked blocks. // TODO: See if we can move the apply_block_hooks_to_content_from_post_object() call // before the parse_blocks() call further above, to avoid the extra serialization/parsing. $markup = serialize_blocks( $blocks ); $markup = apply_block_hooks_to_content_from_post_object( $markup, $navigation_post ); $blocks = parse_blocks( $markup ); // TODO - this uses the full navigation block attributes for the // context which could be refined. return new WP_Block_List( $blocks, $attributes ); } } /** * Gets the inner blocks for the navigation block from the fallback. * * @since 6.5.0 * * @param array $attributes The block attributes. * @return WP_Block_List Returns the inner blocks for the navigation block. */ private static function get_inner_blocks_from_fallback( $attributes ) { $fallback_blocks = block_core_navigation_get_fallback_blocks(); // Fallback my have been filtered so do basic test for validity. if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) { return new WP_Block_List( array(), $attributes ); } return new WP_Block_List( $fallback_blocks, $attributes ); } /** * Gets the inner blocks for the navigation block. * * @since 6.5.0 * * @param array $attributes The block attributes. * @param WP_Block $block The parsed block. * @return WP_Block_List Returns the inner blocks for the navigation block. */ private static function get_inner_blocks( $attributes, $block ) { $inner_blocks = $block->inner_blocks; // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render. if ( array_key_exists( 'navigationMenuId', $attributes ) ) { $attributes['ref'] = $attributes['navigationMenuId']; } // If: // - the gutenberg plugin is active // - `__unstableLocation` is defined // - we have menu items at the defined location // - we don't have a relationship to a `wp_navigation` Post (via `ref`). // ...then create inner blocks from the classic menu assigned to that location. if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && array_key_exists( '__unstableLocation', $attributes ) && ! array_key_exists( 'ref', $attributes ) && ! empty( block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) ) ) { $inner_blocks = block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ); } // Load inner blocks from the navigation post. if ( array_key_exists( 'ref', $attributes ) ) { $inner_blocks = static::get_inner_blocks_from_navigation_post( $attributes ); } // If there are no inner blocks then fallback to rendering an appropriate fallback. if ( empty( $inner_blocks ) ) { $inner_blocks = static::get_inner_blocks_from_fallback( $attributes ); } /** * Filter navigation block $inner_blocks. * Allows modification of a navigation block menu items. * * @since 6.1.0 * * @param \WP_Block_List $inner_blocks */ $inner_blocks = apply_filters( 'block_core_navigation_render_inner_blocks', $inner_blocks ); $post_ids = block_core_navigation_get_post_ids( $inner_blocks ); if ( $post_ids ) { _prime_post_caches( $post_ids, false, false ); } return $inner_blocks; } /** * Gets the name of the current navigation, if it has one. * * @since 6.5.0 * * @param array $attributes The block attributes. * @return string Returns the name of the navigation. */ private static function get_navigation_name( $attributes ) { $navigation_name = $attributes['ariaLabel'] ?? ''; if ( ! empty( $navigation_name ) ) { return $navigation_name; } // Load the navigation post. if ( array_key_exists( 'ref', $attributes ) ) { $navigation_post = get_post( $attributes['ref'] ); if ( ! isset( $navigation_post ) ) { return $navigation_name; } // Only published posts are valid. If this is changed then a corresponding change // must also be implemented in `use-navigation-menu.js`. if ( 'publish' === $navigation_post->post_status ) { $navigation_name = $navigation_post->post_title; // This is used to count the number of times a navigation name has been seen, // so that we can ensure every navigation has a unique id. if ( isset( static::$seen_menu_names[ $navigation_name ] ) ) { ++static::$seen_menu_names[ $navigation_name ]; } else { static::$seen_menu_names[ $navigation_name ] = 1; } } } return $navigation_name; } /** * Returns the layout class for the navigation block. * * @since 6.5.0 * * @param array $attributes The block attributes. * @return string Returns the layout class for the navigation block. */ private static function get_layout_class( $attributes ) { $layout_justification = array( 'left' => 'items-justified-left', 'right' => 'items-justified-right', 'center' => 'items-justified-center', 'space-between' => 'items-justified-space-between', ); $layout_class = ''; if ( isset( $attributes['layout']['justifyContent'] ) && isset( $layout_justification[ $attributes['layout']['justifyContent'] ] ) ) { $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ]; } if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) { $layout_class .= ' is-vertical'; } if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) { $layout_class .= ' no-wrap'; } return $layout_class; } /** * Return classes for the navigation block. * * @since 6.5.0 * * @param array $attributes The block attributes. * @return string Returns the classes for the navigation block. */ private static function get_classes( $attributes ) { // Restore legacy classnames for submenu positioning. $layout_class = static::get_layout_class( $attributes ); $colors = block_core_navigation_build_css_colors( $attributes ); $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); $is_responsive_menu = static::is_responsive( $attributes ); // Manually add block support text decoration as CSS class. $text_decoration = $attributes['style']['typography']['textDecoration'] ?? null; $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration ); $classes = array_merge( $colors['css_classes'], $font_sizes['css_classes'], $is_responsive_menu ? array( 'is-responsive' ) : array(), $layout_class ? array( $layout_class ) : array(), $text_decoration ? array( $text_decoration_class ) : array() ); return implode( ' ', $classes ); } /** * Get styles for the navigation block. * * @since 6.5.0 * * @param array $attributes The block attributes. * @return string Returns the styles for the navigation block. */ private static function get_styles( $attributes ) { $colors = block_core_navigation_build_css_colors( $attributes ); $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : ''; return $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles']; } /** * Get the responsive container markup * * @since 6.5.0 * * @param array $attributes The block attributes. * @param WP_Block_List $inner_blocks The list of inner blocks. * @param string $inner_blocks_html The markup for the inner blocks. * @return string Returns the container markup. */ private static function get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ) { $is_interactive = static::is_interactive( $attributes, $inner_blocks ); $colors = block_core_navigation_build_css_colors( $attributes ); $modal_unique_id = wp_unique_id( 'modal-' ); $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu']; $responsive_container_classes = array( 'wp-block-navigation__responsive-container', $is_hidden_by_default ? 'hidden-by-default' : '', implode( ' ', $colors['overlay_css_classes'] ), ); $open_button_classes = array( 'wp-block-navigation__responsive-container-open', $is_hidden_by_default ? 'always-shown' : '', ); $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon']; $toggle_button_icon = ''; if ( isset( $attributes['icon'] ) ) { if ( 'menu' === $attributes['icon'] ) { $toggle_button_icon = ''; } } $toggle_button_content = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' ); $toggle_close_button_icon = ''; $toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' ); $toggle_aria_label_open = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label. $toggle_aria_label_close = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label. // Add Interactivity API directives to the markup if needed. $open_button_directives = ''; $responsive_container_directives = ''; $responsive_dialog_directives = ''; $close_button_directives = ''; if ( $is_interactive ) { $open_button_directives = ' data-wp-on-async--click="actions.openMenuOnClick" data-wp-on--keydown="actions.handleMenuKeydown" '; $responsive_container_directives = ' data-wp-class--has-modal-open="state.isMenuOpen" data-wp-class--is-menu-open="state.isMenuOpen" data-wp-watch="callbacks.initMenu" data-wp-on--keydown="actions.handleMenuKeydown" data-wp-on-async--focusout="actions.handleMenuFocusout" tabindex="-1" '; $responsive_dialog_directives = ' data-wp-bind--aria-modal="state.ariaModal" data-wp-bind--aria-label="state.ariaLabel" data-wp-bind--role="state.roleAttribute" '; $close_button_directives = ' data-wp-on-async--click="actions.closeMenuOnClick" '; $responsive_container_content_directives = ' data-wp-watch="callbacks.focusFirstElement" '; } $overlay_inline_styles = esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ); return sprintf( '
          %2$s
          ', esc_attr( $modal_unique_id ), $inner_blocks_html, $toggle_aria_label_open, $toggle_aria_label_close, esc_attr( trim( implode( ' ', $responsive_container_classes ) ) ), esc_attr( trim( implode( ' ', $open_button_classes ) ) ), ( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '', $toggle_button_content, $toggle_close_button_content, $open_button_directives, $responsive_container_directives, $responsive_dialog_directives, $close_button_directives, $responsive_container_content_directives ); } /** * Get the wrapper attributes * * @since 6.5.0 * * @param array $attributes The block attributes. * @param WP_Block_List $inner_blocks A list of inner blocks. * @return string Returns the navigation block markup. */ private static function get_nav_wrapper_attributes( $attributes, $inner_blocks ) { $nav_menu_name = static::get_unique_navigation_name( $attributes ); $is_interactive = static::is_interactive( $attributes, $inner_blocks ); $is_responsive_menu = static::is_responsive( $attributes ); $style = static::get_styles( $attributes ); $class = static::get_classes( $attributes ); $extra_attributes = array( 'class' => $class, 'style' => $style, ); if ( ! empty( $nav_menu_name ) ) { $extra_attributes['aria-label'] = $nav_menu_name; } $wrapper_attributes = get_block_wrapper_attributes( $extra_attributes ); if ( $is_responsive_menu ) { $nav_element_directives = static::get_nav_element_directives( $is_interactive ); $wrapper_attributes .= ' ' . $nav_element_directives; } return $wrapper_attributes; } /** * Gets the nav element directives. * * @since 6.5.0 * * @param bool $is_interactive Whether the block is interactive. * @return string the directives for the navigation element. */ private static function get_nav_element_directives( $is_interactive ) { if ( ! $is_interactive ) { return ''; } // When adding to this array be mindful of security concerns. $nav_element_context = wp_interactivity_data_wp_context( array( 'overlayOpenedBy' => array( 'click' => false, 'hover' => false, 'focus' => false, ), 'type' => 'overlay', 'roleAttribute' => '', 'ariaLabel' => __( 'Menu' ), ) ); $nav_element_directives = ' data-wp-interactive="core/navigation" ' . $nav_element_context; return $nav_element_directives; } /** * Handle view script module loading. * * @since 6.5.0 * * @param array $attributes The block attributes. * @param WP_Block $block The parsed block. * @param WP_Block_List $inner_blocks The list of inner blocks. */ private static function handle_view_script_module_loading( $attributes, $block, $inner_blocks ) { if ( static::is_interactive( $attributes, $inner_blocks ) ) { wp_enqueue_script_module( '@wordpress/block-library/navigation/view' ); } } /** * Returns the markup for the navigation block. * * @since 6.5.0 * * @param array $attributes The block attributes. * @param WP_Block_List $inner_blocks The list of inner blocks. * @return string Returns the navigation wrapper markup. */ private static function get_wrapper_markup( $attributes, $inner_blocks ) { $inner_blocks_html = static::get_inner_blocks_html( $attributes, $inner_blocks ); if ( static::is_responsive( $attributes ) ) { return static::get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ); } return $inner_blocks_html; } /** * Returns a unique name for the navigation. * * @since 6.5.0 * * @param array $attributes The block attributes. * @return string Returns a unique name for the navigation. */ private static function get_unique_navigation_name( $attributes ) { $nav_menu_name = static::get_navigation_name( $attributes ); // If the menu name has been used previously then append an ID // to the name to ensure uniqueness across a given post. if ( isset( static::$seen_menu_names[ $nav_menu_name ] ) && static::$seen_menu_names[ $nav_menu_name ] > 1 ) { $count = static::$seen_menu_names[ $nav_menu_name ]; $nav_menu_name = $nav_menu_name . ' ' . ( $count ); } return $nav_menu_name; } /** * Renders the navigation block. * * @since 6.5.0 * * @param array $attributes The block attributes. * @param string $content The saved content. * @param WP_Block $block The parsed block. * @return string Returns the navigation block markup. */ public static function render( $attributes, $content, $block ) { /** * Deprecated: * The rgbTextColor and rgbBackgroundColor attributes * have been deprecated in favor of * customTextColor and customBackgroundColor ones. * Move the values from old attrs to the new ones. */ if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) { $attributes['customTextColor'] = $attributes['rgbTextColor']; } if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) { $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor']; } unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] ); $inner_blocks = static::get_inner_blocks( $attributes, $block ); // Prevent navigation blocks referencing themselves from rendering. if ( block_core_navigation_block_contains_core_navigation( $inner_blocks ) ) { return ''; } static::handle_view_script_module_loading( $attributes, $block, $inner_blocks ); return sprintf( '', static::get_nav_wrapper_attributes( $attributes, $inner_blocks ), static::get_wrapper_markup( $attributes, $inner_blocks ) ); } } // These functions are used for the __unstableLocation feature and only active // when the gutenberg plugin is active. if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { /** * Returns the menu items for a WordPress menu location. * * @since 5.9.0 * * @param string $location The menu location. * @return array Menu items for the location. */ function block_core_navigation_get_menu_items_at_location( $location ) { if ( empty( $location ) ) { return; } // Build menu data. The following approximates the code in // `wp_nav_menu()` and `gutenberg_output_block_nav_menu`. // Find the location in the list of locations, returning early if the // location can't be found. $locations = get_nav_menu_locations(); if ( ! isset( $locations[ $location ] ) ) { return; } // Get the menu from the location, returning early if there is no // menu or there was an error. $menu = wp_get_nav_menu_object( $locations[ $location ] ); if ( ! $menu || is_wp_error( $menu ) ) { return; } $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) ); _wp_menu_item_classes_by_context( $menu_items ); return $menu_items; } /** * Sorts a standard array of menu items into a nested structure keyed by the * id of the parent menu. * * @since 5.9.0 * * @param array $menu_items Menu items to sort. * @return array An array keyed by the id of the parent menu where each element * is an array of menu items that belong to that parent. */ function block_core_navigation_sort_menu_items_by_parent_id( $menu_items ) { $sorted_menu_items = array(); foreach ( (array) $menu_items as $menu_item ) { $sorted_menu_items[ $menu_item->menu_order ] = $menu_item; } unset( $menu_items, $menu_item ); $menu_items_by_parent_id = array(); foreach ( $sorted_menu_items as $menu_item ) { $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item; } return $menu_items_by_parent_id; } /** * Gets the inner blocks for the navigation block from the unstable location attribute. * * @since 6.5.0 * * @param array $attributes The block attributes. * @return WP_Block_List Returns the inner blocks for the navigation block. */ function block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ) { $menu_items = block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ); if ( empty( $menu_items ) ) { return new WP_Block_List( array(), $attributes ); } $menu_items_by_parent_id = block_core_navigation_sort_menu_items_by_parent_id( $menu_items ); $parsed_blocks = block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id ); return new WP_Block_List( $parsed_blocks, $attributes ); } } /** * Add Interactivity API directives to the navigation-submenu and page-list * blocks markup using the Tag Processor. * * @since 6.3.0 * * @param WP_HTML_Tag_Processor $tags Markup of the navigation block. * @param array $block_attributes Block attributes. * * @return string Submenu markup with the directives injected. */ function block_core_navigation_add_directives_to_submenu( $tags, $block_attributes ) { while ( $tags->next_tag( array( 'tag_name' => 'LI', 'class_name' => 'has-child', ) ) ) { // Add directives to the parent `
        • `. $tags->set_attribute( 'data-wp-interactive', 'core/navigation' ); $tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu", "modal": null }' ); $tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' ); $tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' ); $tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' ); // This is a fix for Safari. Without it, Safari doesn't change the active // element when the user clicks on a button. It can be removed once we add // an overlay to capture the clicks, instead of relying on the focusout // event. $tags->set_attribute( 'tabindex', '-1' ); if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) { $tags->set_attribute( 'data-wp-on-async--mouseenter', 'actions.openMenuOnHover' ); $tags->set_attribute( 'data-wp-on-async--mouseleave', 'actions.closeMenuOnHover' ); } // Add directives to the toggle submenu button. if ( $tags->next_tag( array( 'tag_name' => 'BUTTON', 'class_name' => 'wp-block-navigation-submenu__toggle', ) ) ) { $tags->set_attribute( 'data-wp-on-async--click', 'actions.toggleMenuOnClick' ); $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isMenuOpen' ); // The `aria-expanded` attribute for SSR is already added in the submenu block. } // Add directives to the submenu. if ( $tags->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'wp-block-navigation__submenu-container', ) ) ) { $tags->set_attribute( 'data-wp-on-async--focus', 'actions.openMenuOnFocus' ); } // Iterate through subitems if exist. block_core_navigation_add_directives_to_submenu( $tags, $block_attributes ); } return $tags->get_updated_html(); } /** * Build an array with CSS classes and inline styles defining the colors * which will be applied to the navigation markup in the front-end. * * @since 5.9.0 * * @param array $attributes Navigation block attributes. * * @return array Colors CSS classes and inline styles. */ function block_core_navigation_build_css_colors( $attributes ) { $colors = array( 'css_classes' => array(), 'inline_styles' => '', 'overlay_css_classes' => array(), 'overlay_inline_styles' => '', ); // Text color. $has_named_text_color = array_key_exists( 'textColor', $attributes ); $has_custom_text_color = array_key_exists( 'customTextColor', $attributes ); // If has text color. if ( $has_custom_text_color || $has_named_text_color ) { // Add has-text-color class. $colors['css_classes'][] = 'has-text-color'; } if ( $has_named_text_color ) { // Add the color class. $colors['css_classes'][] = sprintf( 'has-%s-color', $attributes['textColor'] ); } elseif ( $has_custom_text_color ) { // Add the custom color inline style. $colors['inline_styles'] .= sprintf( 'color: %s;', $attributes['customTextColor'] ); } // Background color. $has_named_background_color = array_key_exists( 'backgroundColor', $attributes ); $has_custom_background_color = array_key_exists( 'customBackgroundColor', $attributes ); // If has background color. if ( $has_custom_background_color || $has_named_background_color ) { // Add has-background class. $colors['css_classes'][] = 'has-background'; } if ( $has_named_background_color ) { // Add the background-color class. $colors['css_classes'][] = sprintf( 'has-%s-background-color', $attributes['backgroundColor'] ); } elseif ( $has_custom_background_color ) { // Add the custom background-color inline style. $colors['inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customBackgroundColor'] ); } // Overlay text color. $has_named_overlay_text_color = array_key_exists( 'overlayTextColor', $attributes ); $has_custom_overlay_text_color = array_key_exists( 'customOverlayTextColor', $attributes ); // If has overlay text color. if ( $has_custom_overlay_text_color || $has_named_overlay_text_color ) { // Add has-text-color class. $colors['overlay_css_classes'][] = 'has-text-color'; } if ( $has_named_overlay_text_color ) { // Add the overlay color class. $colors['overlay_css_classes'][] = sprintf( 'has-%s-color', $attributes['overlayTextColor'] ); } elseif ( $has_custom_overlay_text_color ) { // Add the custom overlay color inline style. $colors['overlay_inline_styles'] .= sprintf( 'color: %s;', $attributes['customOverlayTextColor'] ); } // Overlay background color. $has_named_overlay_background_color = array_key_exists( 'overlayBackgroundColor', $attributes ); $has_custom_overlay_background_color = array_key_exists( 'customOverlayBackgroundColor', $attributes ); // If has overlay background color. if ( $has_custom_overlay_background_color || $has_named_overlay_background_color ) { // Add has-background class. $colors['overlay_css_classes'][] = 'has-background'; } if ( $has_named_overlay_background_color ) { // Add the overlay background-color class. $colors['overlay_css_classes'][] = sprintf( 'has-%s-background-color', $attributes['overlayBackgroundColor'] ); } elseif ( $has_custom_overlay_background_color ) { // Add the custom overlay background-color inline style. $colors['overlay_inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customOverlayBackgroundColor'] ); } return $colors; } /** * Build an array with CSS classes and inline styles defining the font sizes * which will be applied to the navigation markup in the front-end. * * @since 5.9.0 * * @param array $attributes Navigation block attributes. * * @return array Font size CSS classes and inline styles. */ function block_core_navigation_build_css_font_sizes( $attributes ) { // CSS classes. $font_sizes = array( 'css_classes' => array(), 'inline_styles' => '', ); $has_named_font_size = array_key_exists( 'fontSize', $attributes ); $has_custom_font_size = array_key_exists( 'customFontSize', $attributes ); if ( $has_named_font_size ) { // Add the font size class. $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $attributes['fontSize'] ); } elseif ( $has_custom_font_size ) { // Add the custom font size inline style. $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $attributes['customFontSize'] ); } return $font_sizes; } /** * Returns the top-level submenu SVG chevron icon. * * @since 5.9.0 * * @return string */ function block_core_navigation_render_submenu_icon() { return ''; } /** * Filter out empty "null" blocks from the block list. * 'parse_blocks' includes a null block with '\n\n' as the content when * it encounters whitespace. This is not a bug but rather how the parser * is designed. * * @since 5.9.0 * * @param array $parsed_blocks the parsed blocks to be normalized. * @return array the normalized parsed blocks. */ function block_core_navigation_filter_out_empty_blocks( $parsed_blocks ) { $filtered = array_filter( $parsed_blocks, static function ( $block ) { return isset( $block['blockName'] ); } ); // Reset keys. return array_values( $filtered ); } /** * Returns true if the navigation block contains a nested navigation block. * * @since 6.2.0 * * @param WP_Block_List $inner_blocks Inner block instance to be normalized. * @return bool true if the navigation block contains a nested navigation block. */ function block_core_navigation_block_contains_core_navigation( $inner_blocks ) { foreach ( $inner_blocks as $block ) { if ( 'core/navigation' === $block->name ) { return true; } if ( $block->inner_blocks && block_core_navigation_block_contains_core_navigation( $block->inner_blocks ) ) { return true; } } return false; } /** * Retrieves the appropriate fallback to be used on the front of the * site when there is no menu assigned to the Nav block. * * This aims to mirror how the fallback mechanic for wp_nav_menu works. * See https://developer.wordpress.org/reference/functions/wp_nav_menu/#more-information. * * @since 5.9.0 * * @return array the array of blocks to be used as a fallback. */ function block_core_navigation_get_fallback_blocks() { $page_list_fallback = array( array( 'blockName' => 'core/page-list', 'innerContent' => array(), 'attrs' => array(), ), ); $registry = WP_Block_Type_Registry::get_instance(); // If `core/page-list` is not registered then return empty blocks. $fallback_blocks = $registry->is_registered( 'core/page-list' ) ? $page_list_fallback : array(); $navigation_post = WP_Navigation_Fallback::get_fallback(); // Use the first non-empty Navigation as fallback if available. if ( $navigation_post ) { $parsed_blocks = parse_blocks( $navigation_post->post_content ); $maybe_fallback = block_core_navigation_filter_out_empty_blocks( $parsed_blocks ); // Normalizing blocks may result in an empty array of blocks if they were all `null` blocks. // In this case default to the (Page List) fallback. $fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks; // Run Block Hooks algorithm to inject hooked blocks. // We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks. // TODO: See if we can move the apply_block_hooks_to_content_from_post_object() call // before the parse_blocks() call further above, to avoid the extra serialization/parsing. $markup = serialize_blocks( $fallback_blocks ); $markup = apply_block_hooks_to_content_from_post_object( $markup, $navigation_post ); $fallback_blocks = parse_blocks( $markup ); } /** * Filters the fallback experience for the Navigation block. * * Returning a falsey value will opt out of the fallback and cause the block not to render. * To customise the blocks provided return an array of blocks - these should be valid * children of the `core/navigation` block. * * @since 5.9.0 * * @param array[] $fallback_blocks default fallback blocks provided by the default block mechanic. */ return apply_filters( 'block_core_navigation_render_fallback', $fallback_blocks ); } /** * Iterate through all inner blocks recursively and get navigation link block's post IDs. * * @since 6.0.0 * * @param WP_Block_List $inner_blocks Block list class instance. * * @return array Array of post IDs. */ function block_core_navigation_get_post_ids( $inner_blocks ) { $post_ids = array_map( 'block_core_navigation_from_block_get_post_ids', iterator_to_array( $inner_blocks ) ); return array_unique( array_merge( ...$post_ids ) ); } /** * Get post IDs from a navigation link block instance. * * @since 6.0.0 * * @param WP_Block $block Instance of a block. * * @return array Array of post IDs. */ function block_core_navigation_from_block_get_post_ids( $block ) { $post_ids = array(); if ( $block->inner_blocks ) { $post_ids = block_core_navigation_get_post_ids( $block->inner_blocks ); } if ( 'core/navigation-link' === $block->name || 'core/navigation-submenu' === $block->name ) { if ( $block->attributes && isset( $block->attributes['kind'] ) && 'post-type' === $block->attributes['kind'] && isset( $block->attributes['id'] ) ) { $post_ids[] = $block->attributes['id']; } } return $post_ids; } /** * Renders the `core/navigation` block on server. * * @since 5.9.0 * * @param array $attributes The block attributes. * @param string $content The saved content. * @param WP_Block $block The parsed block. * * @return string Returns the navigation block markup. */ function render_block_core_navigation( $attributes, $content, $block ) { return WP_Navigation_Block_Renderer::render( $attributes, $content, $block ); } /** * Register the navigation block. * * @since 5.9.0 * * @uses render_block_core_navigation() * @throws WP_Error An WP_Error exception parsing the block definition. */ function register_block_core_navigation() { register_block_type_from_metadata( __DIR__ . '/navigation', array( 'render_callback' => 'render_block_core_navigation', ) ); } add_action( 'init', 'register_block_core_navigation' ); /** * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly. * * @since 5.9.0 * * @param array $parsed_block The block being rendered. * * @return array The block being rendered without typographic presets. */ function block_core_navigation_typographic_presets_backcompatibility( $parsed_block ) { if ( 'core/navigation' === $parsed_block['blockName'] ) { $attribute_to_prefix_map = array( 'fontStyle' => 'var:preset|font-style|', 'fontWeight' => 'var:preset|font-weight|', 'textDecoration' => 'var:preset|text-decoration|', 'textTransform' => 'var:preset|text-transform|', ); foreach ( $attribute_to_prefix_map as $style_attribute => $prefix ) { if ( ! empty( $parsed_block['attrs']['style']['typography'][ $style_attribute ] ) ) { $prefix_len = strlen( $prefix ); $attribute_value = &$parsed_block['attrs']['style']['typography'][ $style_attribute ]; if ( 0 === strncmp( $attribute_value, $prefix, $prefix_len ) ) { $attribute_value = substr( $attribute_value, $prefix_len ); } if ( 'textDecoration' === $style_attribute && 'strikethrough' === $attribute_value ) { $attribute_value = 'line-through'; } } } } return $parsed_block; } add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' ); /** * Turns menu item data into a nested array of parsed blocks * * @since 5.9.0 * * @deprecated 6.3.0 Use WP_Navigation_Fallback::parse_blocks_from_menu_items() instead. * * @param array $menu_items An array of menu items that represent * an individual level of a menu. * @param array $menu_items_by_parent_id An array keyed by the id of the * parent menu where each element is an * array of menu items that belong to * that parent. * @return array An array of parsed block data. */ function block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) { _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::parse_blocks_from_menu_items' ); if ( empty( $menu_items ) ) { return array(); } $blocks = array(); foreach ( $menu_items as $menu_item ) { $class_name = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null; $id = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null; $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target; $rel = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null; $kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom'; $block = array( 'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link', 'attrs' => array( 'className' => $class_name, 'description' => $menu_item->description, 'id' => $id, 'kind' => $kind, 'label' => $menu_item->title, 'opensInNewTab' => $opens_in_new_tab, 'rel' => $rel, 'title' => $menu_item->attr_title, 'type' => $menu_item->object, 'url' => $menu_item->url, ), ); $block['innerBlocks'] = isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[ $menu_item->ID ], $menu_items_by_parent_id ) : array(); $block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] ); $blocks[] = $block; } return $blocks; } /** * Get the classic navigation menu to use as a fallback. * * @since 6.2.0 * * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback() instead. * * @return object WP_Term The classic navigation. */ function block_core_navigation_get_classic_menu_fallback() { _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_classic_menu_fallback' ); $classic_nav_menus = wp_get_nav_menus(); // If menus exist. if ( $classic_nav_menus && ! is_wp_error( $classic_nav_menus ) ) { // Handles simple use case where user has a classic menu and switches to a block theme. // Returns the menu assigned to location `primary`. $locations = get_nav_menu_locations(); if ( isset( $locations['primary'] ) ) { $primary_menu = wp_get_nav_menu_object( $locations['primary'] ); if ( $primary_menu ) { return $primary_menu; } } // Returns a menu if `primary` is its slug. foreach ( $classic_nav_menus as $classic_nav_menu ) { if ( 'primary' === $classic_nav_menu->slug ) { return $classic_nav_menu; } } // Otherwise return the most recently created classic menu. usort( $classic_nav_menus, static function ( $a, $b ) { return $b->term_id - $a->term_id; } ); return $classic_nav_menus[0]; } } /** * Converts a classic navigation to blocks. * * @since 6.2.0 * * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback_blocks() instead. * * @param object $classic_nav_menu WP_Term The classic navigation object to convert. * @return array the normalized parsed blocks. */ function block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ) { _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_classic_menu_fallback_blocks' ); // BEGIN: Code that already exists in wp_nav_menu(). $menu_items = wp_get_nav_menu_items( $classic_nav_menu->term_id, array( 'update_post_term_cache' => false ) ); // Set up the $menu_item variables. _wp_menu_item_classes_by_context( $menu_items ); $sorted_menu_items = array(); foreach ( (array) $menu_items as $menu_item ) { $sorted_menu_items[ $menu_item->menu_order ] = $menu_item; } unset( $menu_items, $menu_item ); // END: Code that already exists in wp_nav_menu(). $menu_items_by_parent_id = array(); foreach ( $sorted_menu_items as $menu_item ) { $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item; } $inner_blocks = block_core_navigation_parse_blocks_from_menu_items( isset( $menu_items_by_parent_id[0] ) ? $menu_items_by_parent_id[0] : array(), $menu_items_by_parent_id ); return serialize_blocks( $inner_blocks ); } /** * If there's a classic menu then use it as a fallback. * * @since 6.2.0 * * @deprecated 6.3.0 Use WP_Navigation_Fallback::create_classic_menu_fallback() instead. * * @return array the normalized parsed blocks. */ function block_core_navigation_maybe_use_classic_menu_fallback() { _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::create_classic_menu_fallback' ); // See if we have a classic menu. $classic_nav_menu = block_core_navigation_get_classic_menu_fallback(); if ( ! $classic_nav_menu ) { return; } // If we have a classic menu then convert it to blocks. $classic_nav_menu_blocks = block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ); if ( empty( $classic_nav_menu_blocks ) ) { return; } // Create a new navigation menu from the classic menu. $wp_insert_post_result = wp_insert_post( array( 'post_content' => $classic_nav_menu_blocks, 'post_title' => $classic_nav_menu->name, 'post_name' => $classic_nav_menu->slug, 'post_status' => 'publish', 'post_type' => 'wp_navigation', ), true // So that we can check whether the result is an error. ); if ( is_wp_error( $wp_insert_post_result ) ) { return; } // Fetch the most recently published navigation which will be the classic one created above. return block_core_navigation_get_most_recently_published_navigation(); } /** * Finds the most recently published `wp_navigation` Post. * * @since 6.1.0 * * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_most_recently_published_navigation() instead. * * @return WP_Post|null the first non-empty Navigation or null. */ function block_core_navigation_get_most_recently_published_navigation() { _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_most_recently_published_navigation' ); // Default to the most recently created menu. $parsed_args = array( 'post_type' => 'wp_navigation', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'order' => 'DESC', 'orderby' => 'date', 'post_status' => 'publish', 'posts_per_page' => 1, // get only the most recent. ); $navigation_post = new WP_Query( $parsed_args ); if ( count( $navigation_post->posts ) > 0 ) { return $navigation_post->posts[0]; } return null; } PKt\html/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/html", "title": "Custom HTML", "category": "widgets", "description": "Add custom HTML code and preview it as you edit.", "keywords": [ "embed" ], "textdomain": "default", "attributes": { "content": { "type": "string", "source": "raw" } }, "supports": { "customClassName": false, "className": false, "html": false, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-html-editor" } PKt\bthtml/editor.min.cssnu[.block-library-html__edit .block-library-html__preview-overlay{height:100%;left:0;position:absolute;top:0;width:100%}.block-library-html__edit .block-editor-plain-text{background:#fff!important;border:1px solid #1e1e1e!important;border-radius:2px!important;box-shadow:none!important;box-sizing:border-box;color:#1e1e1e!important;direction:ltr;font-family:Menlo,Consolas,monaco,monospace!important;font-size:16px!important;max-height:250px;padding:12px!important}@media (min-width:600px){.block-library-html__edit .block-editor-plain-text{font-size:13px!important}}.block-library-html__edit .block-editor-plain-text:focus{border-color:var(--wp-admin-theme-color)!important;box-shadow:0 0 0 1px var(--wp-admin-theme-color)!important;outline:2px solid #0000!important}PKt\6&YYhtml/editor-rtl.cssnu[.block-library-html__edit .block-library-html__preview-overlay{ height:100%; position:absolute; right:0; top:0; width:100%; } .block-library-html__edit .block-editor-plain-text{ background:#fff !important; border:1px solid #1e1e1e !important; border-radius:2px !important; box-shadow:none !important; box-sizing:border-box; color:#1e1e1e !important; direction:ltr; font-family:Menlo,Consolas,monaco,monospace !important; font-size:16px !important; max-height:250px; padding:12px !important; } @media (min-width:600px){ .block-library-html__edit .block-editor-plain-text{ font-size:13px !important; } } .block-library-html__edit .block-editor-plain-text:focus{ border-color:var(--wp-admin-theme-color) !important; box-shadow:0 0 0 1px var(--wp-admin-theme-color) !important; outline:2px solid #0000 !important; }PKt\Lhtml/editor-rtl.min.cssnu[.block-library-html__edit .block-library-html__preview-overlay{height:100%;position:absolute;right:0;top:0;width:100%}.block-library-html__edit .block-editor-plain-text{background:#fff!important;border:1px solid #1e1e1e!important;border-radius:2px!important;box-shadow:none!important;box-sizing:border-box;color:#1e1e1e!important;direction:ltr;font-family:Menlo,Consolas,monaco,monospace!important;font-size:16px!important;max-height:250px;padding:12px!important}@media (min-width:600px){.block-library-html__edit .block-editor-plain-text{font-size:13px!important}}.block-library-html__edit .block-editor-plain-text:focus{border-color:var(--wp-admin-theme-color)!important;box-shadow:0 0 0 1px var(--wp-admin-theme-color)!important;outline:2px solid #0000!important}PKt\"pXXhtml/editor.cssnu[.block-library-html__edit .block-library-html__preview-overlay{ height:100%; left:0; position:absolute; top:0; width:100%; } .block-library-html__edit .block-editor-plain-text{ background:#fff !important; border:1px solid #1e1e1e !important; border-radius:2px !important; box-shadow:none !important; box-sizing:border-box; color:#1e1e1e !important; direction:ltr; font-family:Menlo,Consolas,monaco,monospace !important; font-size:16px !important; max-height:250px; padding:12px !important; } @media (min-width:600px){ .block-library-html__edit .block-editor-plain-text{ font-size:13px !important; } } .block-library-html__edit .block-editor-plain-text:focus{ border-color:var(--wp-admin-theme-color) !important; box-shadow:0 0 0 1px var(--wp-admin-theme-color) !important; outline:2px solid #0000 !important; }PKt\;'comments-pagination-previous/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/comments-pagination-previous", "title": "Comments Previous Page", "category": "theme", "parent": [ "core/comments-pagination" ], "description": "Displays the previous comment's page link.", "textdomain": "default", "attributes": { "label": { "type": "string" } }, "usesContext": [ "postId", "comments/paginationArrow" ], "supports": { "reusable": false, "html": false, "color": { "gradients": true, "text": false, "__experimentalDefaultControls": { "background": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } } } PKt\O.H' ' social-links/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/social-links", "title": "Social Icons", "category": "widgets", "allowedBlocks": [ "core/social-link" ], "description": "Display icons linking to your social profiles or sites.", "keywords": [ "links" ], "textdomain": "default", "attributes": { "iconColor": { "type": "string" }, "customIconColor": { "type": "string" }, "iconColorValue": { "type": "string" }, "iconBackgroundColor": { "type": "string" }, "customIconBackgroundColor": { "type": "string" }, "iconBackgroundColorValue": { "type": "string" }, "openInNewTab": { "type": "boolean", "default": false }, "showLabels": { "type": "boolean", "default": false }, "size": { "type": "string" } }, "providesContext": { "openInNewTab": "openInNewTab", "showLabels": "showLabels", "iconColor": "iconColor", "iconColorValue": "iconColorValue", "iconBackgroundColor": "iconBackgroundColor", "iconBackgroundColorValue": "iconBackgroundColorValue" }, "supports": { "align": [ "left", "center", "right" ], "anchor": true, "__experimentalExposeControlsToChildren": true, "layout": { "allowSwitching": false, "allowInheriting": false, "allowVerticalAlignment": false, "default": { "type": "flex" } }, "color": { "enableContrastChecker": false, "background": true, "gradients": true, "text": false, "__experimentalDefaultControls": { "background": false } }, "spacing": { "blockGap": [ "horizontal", "vertical" ], "margin": true, "padding": true, "units": [ "px", "em", "rem", "vh", "vw" ], "__experimentalDefaultControls": { "blockGap": true, "margin": true, "padding": false } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "styles": [ { "name": "default", "label": "Default", "isDefault": true }, { "name": "logos-only", "label": "Logos Only" }, { "name": "pill-shape", "label": "Pill Shape" } ], "editorStyle": "wp-block-social-links-editor", "style": "wp-block-social-links" } PKt\social-links/editor.min.cssnu[.wp-block-social-links div.block-editor-url-input{display:inline-block;margin-left:8px}.wp-social-link:hover{transform:none}:root :where(.wp-block-social-links),:root :where(.wp-block-social-links.is-style-logos-only .wp-block-social-links__social-placeholder .wp-social-link){padding:0}:root :where(.wp-block-social-links__social-placeholder .wp-social-link){padding:.25em}:root :where(.wp-block-social-links.is-style-pill-shape .wp-block-social-links__social-placeholder .wp-social-link){padding-left:.6666666667em;padding-right:.6666666667em}.wp-block-social-links__social-placeholder{display:flex;list-style:none;opacity:.8}.wp-block-social-links__social-placeholder>.wp-social-link{margin-left:0!important;margin-right:0!important;padding-left:0!important;padding-right:0!important;visibility:hidden;width:0!important}.wp-block-social-links__social-placeholder>.wp-block-social-links__social-placeholder-icons{display:flex}.wp-block-social-links__social-placeholder .wp-social-link:before{border-radius:50%;content:"";display:block;height:1em;width:1em}.is-style-logos-only .wp-block-social-links__social-placeholder .wp-social-link:before{background:currentColor}.wp-block-social-links .wp-block-social-links__social-prompt{cursor:default;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;line-height:24px;list-style:none;margin-bottom:auto;margin-top:auto;min-height:24px;padding-right:8px}.wp-block.wp-block-social-links.aligncenter,.wp-block[data-align=center]>.wp-block-social-links{justify-content:center}.block-editor-block-preview__content .components-button:disabled{opacity:1}.wp-social-link.wp-social-link__is-incomplete{opacity:.5}.wp-block-social-links .is-selected .wp-social-link__is-incomplete,.wp-social-link.wp-social-link__is-incomplete:focus,.wp-social-link.wp-social-link__is-incomplete:hover{opacity:1}.wp-block-social-links .block-list-appender{position:static}.wp-block-social-links .block-list-appender .block-editor-inserter{font-size:inherit}.wp-block-social-links .block-list-appender .block-editor-button-block-appender{font-size:inherit;height:1.5em;padding:0;width:1.5em}PKt\֢--social-links/style.min.cssnu[.wp-block-social-links{background:none;box-sizing:border-box;margin-left:0;padding-left:0;padding-right:0;text-indent:0}.wp-block-social-links .wp-social-link a,.wp-block-social-links .wp-social-link a:hover{border-bottom:0;box-shadow:none;text-decoration:none}.wp-block-social-links .wp-social-link svg{height:1em;width:1em}.wp-block-social-links .wp-social-link span:not(.screen-reader-text){font-size:.65em;margin-left:.5em;margin-right:.5em}.wp-block-social-links.has-small-icon-size{font-size:16px}.wp-block-social-links,.wp-block-social-links.has-normal-icon-size{font-size:24px}.wp-block-social-links.has-large-icon-size{font-size:36px}.wp-block-social-links.has-huge-icon-size{font-size:48px}.wp-block-social-links.aligncenter{display:flex;justify-content:center}.wp-block-social-links.alignright{justify-content:flex-end}.wp-block-social-link{border-radius:9999px;display:block;height:auto}@media not (prefers-reduced-motion){.wp-block-social-link{transition:transform .1s ease}}.wp-block-social-link a{align-items:center;display:flex;line-height:0}.wp-block-social-link:hover{transform:scale(1.1)}.wp-block-social-links .wp-block-social-link.wp-social-link{display:inline-block;margin:0;padding:0}.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor svg,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:active,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:hover,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:visited{color:currentColor;fill:currentColor}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link{background-color:#f0f0f0;color:#444}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-amazon{background-color:#f90;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-bandcamp{background-color:#1ea0c3;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-behance{background-color:#0757fe;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-bluesky{background-color:#0a7aff;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-codepen{background-color:#1e1f26;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-deviantart{background-color:#02e49b;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-discord{background-color:#5865f2;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-dribbble{background-color:#e94c89;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-dropbox{background-color:#4280ff;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-etsy{background-color:#f45800;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-facebook{background-color:#0866ff;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-fivehundredpx{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-flickr{background-color:#0461dd;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-foursquare{background-color:#e65678;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-github{background-color:#24292d;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-goodreads{background-color:#eceadd;color:#382110}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-google{background-color:#ea4434;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-gravatar{background-color:#1d4fc4;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-instagram{background-color:#f00075;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-lastfm{background-color:#e21b24;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-linkedin{background-color:#0d66c2;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-mastodon{background-color:#3288d4;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-medium{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-meetup{background-color:#f6405f;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-patreon{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-pinterest{background-color:#e60122;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-pocket{background-color:#ef4155;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-reddit{background-color:#ff4500;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-skype{background-color:#0478d7;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-snapchat{background-color:#fefc00;color:#fff;stroke:#000}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-soundcloud{background-color:#ff5600;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-spotify{background-color:#1bd760;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-telegram{background-color:#2aabee;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-threads{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-tiktok{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-tumblr{background-color:#011835;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-twitch{background-color:#6440a4;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-twitter{background-color:#1da1f2;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-vimeo{background-color:#1eb7ea;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-vk{background-color:#4680c2;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-wordpress{background-color:#3499cd;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-whatsapp{background-color:#25d366;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-x{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-yelp{background-color:#d32422;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-youtube{background-color:red;color:#fff}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link{background:none}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link svg{height:1.25em;width:1.25em}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-amazon{color:#f90}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-bandcamp{color:#1ea0c3}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-behance{color:#0757fe}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-bluesky{color:#0a7aff}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-codepen{color:#1e1f26}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-deviantart{color:#02e49b}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-discord{color:#5865f2}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-dribbble{color:#e94c89}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-dropbox{color:#4280ff}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-etsy{color:#f45800}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-facebook{color:#0866ff}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-fivehundredpx{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-flickr{color:#0461dd}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-foursquare{color:#e65678}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-github{color:#24292d}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-goodreads{color:#382110}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-google{color:#ea4434}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-gravatar{color:#1d4fc4}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-instagram{color:#f00075}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-lastfm{color:#e21b24}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-linkedin{color:#0d66c2}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-mastodon{color:#3288d4}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-medium{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-meetup{color:#f6405f}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-patreon{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-pinterest{color:#e60122}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-pocket{color:#ef4155}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-reddit{color:#ff4500}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-skype{color:#0478d7}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-snapchat{color:#fff;stroke:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-soundcloud{color:#ff5600}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-spotify{color:#1bd760}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-telegram{color:#2aabee}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-threads{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-tiktok{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-tumblr{color:#011835}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-twitch{color:#6440a4}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-twitter{color:#1da1f2}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-vimeo{color:#1eb7ea}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-vk{color:#4680c2}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-whatsapp{color:#25d366}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-wordpress{color:#3499cd}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-x{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-yelp{color:#d32422}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-youtube{color:red}.wp-block-social-links.is-style-pill-shape .wp-social-link{width:auto}:root :where(.wp-block-social-links .wp-social-link a){padding:.25em}:root :where(.wp-block-social-links.is-style-logos-only .wp-social-link a){padding:0}:root :where(.wp-block-social-links.is-style-pill-shape .wp-social-link a){padding-left:.6666666667em;padding-right:.6666666667em}.wp-block-social-links:not(.has-icon-color):not(.has-icon-background-color) .wp-social-link-snapchat .wp-block-social-link-label{color:#000}PKt\l&ze1e1social-links/style-rtl.cssnu[.wp-block-social-links{ background:none; box-sizing:border-box; margin-right:0; padding-left:0; padding-right:0; text-indent:0; } .wp-block-social-links .wp-social-link a,.wp-block-social-links .wp-social-link a:hover{ border-bottom:0; box-shadow:none; text-decoration:none; } .wp-block-social-links .wp-social-link svg{ height:1em; width:1em; } .wp-block-social-links .wp-social-link span:not(.screen-reader-text){ font-size:.65em; margin-left:.5em; margin-right:.5em; } .wp-block-social-links.has-small-icon-size{ font-size:16px; } .wp-block-social-links,.wp-block-social-links.has-normal-icon-size{ font-size:24px; } .wp-block-social-links.has-large-icon-size{ font-size:36px; } .wp-block-social-links.has-huge-icon-size{ font-size:48px; } .wp-block-social-links.aligncenter{ display:flex; justify-content:center; } .wp-block-social-links.alignright{ justify-content:flex-end; } .wp-block-social-link{ border-radius:9999px; display:block; height:auto; } @media not (prefers-reduced-motion){ .wp-block-social-link{ transition:transform .1s ease; } } .wp-block-social-link a{ align-items:center; display:flex; line-height:0; } .wp-block-social-link:hover{ transform:scale(1.1); } .wp-block-social-links .wp-block-social-link.wp-social-link{ display:inline-block; margin:0; padding:0; } .wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor svg,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:active,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:hover,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:visited{ color:currentColor; fill:currentColor; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link{ background-color:#f0f0f0; color:#444; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-amazon{ background-color:#f90; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-bandcamp{ background-color:#1ea0c3; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-behance{ background-color:#0757fe; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-bluesky{ background-color:#0a7aff; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-codepen{ background-color:#1e1f26; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-deviantart{ background-color:#02e49b; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-discord{ background-color:#5865f2; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-dribbble{ background-color:#e94c89; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-dropbox{ background-color:#4280ff; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-etsy{ background-color:#f45800; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-facebook{ background-color:#0866ff; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-fivehundredpx{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-flickr{ background-color:#0461dd; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-foursquare{ background-color:#e65678; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-github{ background-color:#24292d; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-goodreads{ background-color:#eceadd; color:#382110; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-google{ background-color:#ea4434; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-gravatar{ background-color:#1d4fc4; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-instagram{ background-color:#f00075; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-lastfm{ background-color:#e21b24; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-linkedin{ background-color:#0d66c2; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-mastodon{ background-color:#3288d4; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-medium{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-meetup{ background-color:#f6405f; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-patreon{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-pinterest{ background-color:#e60122; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-pocket{ background-color:#ef4155; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-reddit{ background-color:#ff4500; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-skype{ background-color:#0478d7; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-snapchat{ background-color:#fefc00; color:#fff; stroke:#000; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-soundcloud{ background-color:#ff5600; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-spotify{ background-color:#1bd760; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-telegram{ background-color:#2aabee; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-threads{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-tiktok{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-tumblr{ background-color:#011835; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-twitch{ background-color:#6440a4; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-twitter{ background-color:#1da1f2; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-vimeo{ background-color:#1eb7ea; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-vk{ background-color:#4680c2; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-wordpress{ background-color:#3499cd; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-whatsapp{ background-color:#25d366; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-x{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-yelp{ background-color:#d32422; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-youtube{ background-color:red; color:#fff; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link{ background:none; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link svg{ height:1.25em; width:1.25em; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-amazon{ color:#f90; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-bandcamp{ color:#1ea0c3; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-behance{ color:#0757fe; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-bluesky{ color:#0a7aff; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-codepen{ color:#1e1f26; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-deviantart{ color:#02e49b; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-discord{ color:#5865f2; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-dribbble{ color:#e94c89; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-dropbox{ color:#4280ff; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-etsy{ color:#f45800; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-facebook{ color:#0866ff; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-fivehundredpx{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-flickr{ color:#0461dd; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-foursquare{ color:#e65678; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-github{ color:#24292d; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-goodreads{ color:#382110; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-google{ color:#ea4434; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-gravatar{ color:#1d4fc4; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-instagram{ color:#f00075; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-lastfm{ color:#e21b24; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-linkedin{ color:#0d66c2; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-mastodon{ color:#3288d4; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-medium{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-meetup{ color:#f6405f; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-patreon{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-pinterest{ color:#e60122; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-pocket{ color:#ef4155; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-reddit{ color:#ff4500; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-skype{ color:#0478d7; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-snapchat{ color:#fff; stroke:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-soundcloud{ color:#ff5600; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-spotify{ color:#1bd760; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-telegram{ color:#2aabee; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-threads{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-tiktok{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-tumblr{ color:#011835; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-twitch{ color:#6440a4; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-twitter{ color:#1da1f2; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-vimeo{ color:#1eb7ea; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-vk{ color:#4680c2; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-whatsapp{ color:#25d366; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-wordpress{ color:#3499cd; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-x{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-yelp{ color:#d32422; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-youtube{ color:red; } .wp-block-social-links.is-style-pill-shape .wp-social-link{ width:auto; } :root :where(.wp-block-social-links .wp-social-link a){ padding:.25em; } :root :where(.wp-block-social-links.is-style-logos-only .wp-social-link a){ padding:0; } :root :where(.wp-block-social-links.is-style-pill-shape .wp-social-link a){ padding-left:.6666666667em; padding-right:.6666666667em; } .wp-block-social-links:not(.has-icon-color):not(.has-icon-background-color) .wp-social-link-snapchat .wp-block-social-link-label{ color:#000; }PKt\O O social-links/editor-rtl.cssnu[.wp-block-social-links div.block-editor-url-input{ display:inline-block; margin-right:8px; } .wp-social-link:hover{ transform:none; } :root :where(.wp-block-social-links),:root :where(.wp-block-social-links.is-style-logos-only .wp-block-social-links__social-placeholder .wp-social-link){ padding:0; } :root :where(.wp-block-social-links__social-placeholder .wp-social-link){ padding:.25em; } :root :where(.wp-block-social-links.is-style-pill-shape .wp-block-social-links__social-placeholder .wp-social-link){ padding-left:.6666666667em; padding-right:.6666666667em; } .wp-block-social-links__social-placeholder{ display:flex; list-style:none; opacity:.8; } .wp-block-social-links__social-placeholder>.wp-social-link{ margin-left:0 !important; margin-right:0 !important; padding-left:0 !important; padding-right:0 !important; visibility:hidden; width:0 !important; } .wp-block-social-links__social-placeholder>.wp-block-social-links__social-placeholder-icons{ display:flex; } .wp-block-social-links__social-placeholder .wp-social-link:before{ border-radius:50%; content:""; display:block; height:1em; width:1em; } .is-style-logos-only .wp-block-social-links__social-placeholder .wp-social-link:before{ background:currentColor; } .wp-block-social-links .wp-block-social-links__social-prompt{ cursor:default; font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif; font-size:13px; line-height:24px; list-style:none; margin-bottom:auto; margin-top:auto; min-height:24px; padding-left:8px; } .wp-block.wp-block-social-links.aligncenter,.wp-block[data-align=center]>.wp-block-social-links{ justify-content:center; } .block-editor-block-preview__content .components-button:disabled{ opacity:1; } .wp-social-link.wp-social-link__is-incomplete{ opacity:.5; } .wp-block-social-links .is-selected .wp-social-link__is-incomplete,.wp-social-link.wp-social-link__is-incomplete:focus,.wp-social-link.wp-social-link__is-incomplete:hover{ opacity:1; } .wp-block-social-links .block-list-appender{ position:static; } .wp-block-social-links .block-list-appender .block-editor-inserter{ font-size:inherit; } .wp-block-social-links .block-list-appender .block-editor-button-block-appender{ font-size:inherit; height:1.5em; padding:0; width:1.5em; }PKt\>Fd1d1social-links/style.cssnu[.wp-block-social-links{ background:none; box-sizing:border-box; margin-left:0; padding-left:0; padding-right:0; text-indent:0; } .wp-block-social-links .wp-social-link a,.wp-block-social-links .wp-social-link a:hover{ border-bottom:0; box-shadow:none; text-decoration:none; } .wp-block-social-links .wp-social-link svg{ height:1em; width:1em; } .wp-block-social-links .wp-social-link span:not(.screen-reader-text){ font-size:.65em; margin-left:.5em; margin-right:.5em; } .wp-block-social-links.has-small-icon-size{ font-size:16px; } .wp-block-social-links,.wp-block-social-links.has-normal-icon-size{ font-size:24px; } .wp-block-social-links.has-large-icon-size{ font-size:36px; } .wp-block-social-links.has-huge-icon-size{ font-size:48px; } .wp-block-social-links.aligncenter{ display:flex; justify-content:center; } .wp-block-social-links.alignright{ justify-content:flex-end; } .wp-block-social-link{ border-radius:9999px; display:block; height:auto; } @media not (prefers-reduced-motion){ .wp-block-social-link{ transition:transform .1s ease; } } .wp-block-social-link a{ align-items:center; display:flex; line-height:0; } .wp-block-social-link:hover{ transform:scale(1.1); } .wp-block-social-links .wp-block-social-link.wp-social-link{ display:inline-block; margin:0; padding:0; } .wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor svg,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:active,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:hover,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:visited{ color:currentColor; fill:currentColor; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link{ background-color:#f0f0f0; color:#444; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-amazon{ background-color:#f90; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-bandcamp{ background-color:#1ea0c3; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-behance{ background-color:#0757fe; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-bluesky{ background-color:#0a7aff; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-codepen{ background-color:#1e1f26; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-deviantart{ background-color:#02e49b; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-discord{ background-color:#5865f2; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-dribbble{ background-color:#e94c89; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-dropbox{ background-color:#4280ff; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-etsy{ background-color:#f45800; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-facebook{ background-color:#0866ff; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-fivehundredpx{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-flickr{ background-color:#0461dd; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-foursquare{ background-color:#e65678; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-github{ background-color:#24292d; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-goodreads{ background-color:#eceadd; color:#382110; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-google{ background-color:#ea4434; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-gravatar{ background-color:#1d4fc4; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-instagram{ background-color:#f00075; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-lastfm{ background-color:#e21b24; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-linkedin{ background-color:#0d66c2; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-mastodon{ background-color:#3288d4; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-medium{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-meetup{ background-color:#f6405f; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-patreon{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-pinterest{ background-color:#e60122; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-pocket{ background-color:#ef4155; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-reddit{ background-color:#ff4500; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-skype{ background-color:#0478d7; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-snapchat{ background-color:#fefc00; color:#fff; stroke:#000; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-soundcloud{ background-color:#ff5600; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-spotify{ background-color:#1bd760; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-telegram{ background-color:#2aabee; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-threads{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-tiktok{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-tumblr{ background-color:#011835; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-twitch{ background-color:#6440a4; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-twitter{ background-color:#1da1f2; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-vimeo{ background-color:#1eb7ea; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-vk{ background-color:#4680c2; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-wordpress{ background-color:#3499cd; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-whatsapp{ background-color:#25d366; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-x{ background-color:#000; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-yelp{ background-color:#d32422; color:#fff; } :where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-youtube{ background-color:red; color:#fff; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link{ background:none; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link svg{ height:1.25em; width:1.25em; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-amazon{ color:#f90; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-bandcamp{ color:#1ea0c3; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-behance{ color:#0757fe; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-bluesky{ color:#0a7aff; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-codepen{ color:#1e1f26; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-deviantart{ color:#02e49b; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-discord{ color:#5865f2; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-dribbble{ color:#e94c89; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-dropbox{ color:#4280ff; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-etsy{ color:#f45800; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-facebook{ color:#0866ff; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-fivehundredpx{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-flickr{ color:#0461dd; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-foursquare{ color:#e65678; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-github{ color:#24292d; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-goodreads{ color:#382110; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-google{ color:#ea4434; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-gravatar{ color:#1d4fc4; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-instagram{ color:#f00075; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-lastfm{ color:#e21b24; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-linkedin{ color:#0d66c2; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-mastodon{ color:#3288d4; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-medium{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-meetup{ color:#f6405f; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-patreon{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-pinterest{ color:#e60122; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-pocket{ color:#ef4155; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-reddit{ color:#ff4500; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-skype{ color:#0478d7; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-snapchat{ color:#fff; stroke:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-soundcloud{ color:#ff5600; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-spotify{ color:#1bd760; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-telegram{ color:#2aabee; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-threads{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-tiktok{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-tumblr{ color:#011835; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-twitch{ color:#6440a4; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-twitter{ color:#1da1f2; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-vimeo{ color:#1eb7ea; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-vk{ color:#4680c2; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-whatsapp{ color:#25d366; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-wordpress{ color:#3499cd; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-x{ color:#000; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-yelp{ color:#d32422; } :where(.wp-block-social-links.is-style-logos-only) .wp-social-link-youtube{ color:red; } .wp-block-social-links.is-style-pill-shape .wp-social-link{ width:auto; } :root :where(.wp-block-social-links .wp-social-link a){ padding:.25em; } :root :where(.wp-block-social-links.is-style-logos-only .wp-social-link a){ padding:0; } :root :where(.wp-block-social-links.is-style-pill-shape .wp-social-link a){ padding-left:.6666666667em; padding-right:.6666666667em; } .wp-block-social-links:not(.has-icon-color):not(.has-icon-background-color) .wp-social-link-snapchat .wp-block-social-link-label{ color:#000; }PKt\r9e2--social-links/style-rtl.min.cssnu[.wp-block-social-links{background:none;box-sizing:border-box;margin-right:0;padding-left:0;padding-right:0;text-indent:0}.wp-block-social-links .wp-social-link a,.wp-block-social-links .wp-social-link a:hover{border-bottom:0;box-shadow:none;text-decoration:none}.wp-block-social-links .wp-social-link svg{height:1em;width:1em}.wp-block-social-links .wp-social-link span:not(.screen-reader-text){font-size:.65em;margin-left:.5em;margin-right:.5em}.wp-block-social-links.has-small-icon-size{font-size:16px}.wp-block-social-links,.wp-block-social-links.has-normal-icon-size{font-size:24px}.wp-block-social-links.has-large-icon-size{font-size:36px}.wp-block-social-links.has-huge-icon-size{font-size:48px}.wp-block-social-links.aligncenter{display:flex;justify-content:center}.wp-block-social-links.alignright{justify-content:flex-end}.wp-block-social-link{border-radius:9999px;display:block;height:auto}@media not (prefers-reduced-motion){.wp-block-social-link{transition:transform .1s ease}}.wp-block-social-link a{align-items:center;display:flex;line-height:0}.wp-block-social-link:hover{transform:scale(1.1)}.wp-block-social-links .wp-block-social-link.wp-social-link{display:inline-block;margin:0;padding:0}.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor svg,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:active,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:hover,.wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor:visited{color:currentColor;fill:currentColor}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link{background-color:#f0f0f0;color:#444}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-amazon{background-color:#f90;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-bandcamp{background-color:#1ea0c3;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-behance{background-color:#0757fe;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-bluesky{background-color:#0a7aff;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-codepen{background-color:#1e1f26;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-deviantart{background-color:#02e49b;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-discord{background-color:#5865f2;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-dribbble{background-color:#e94c89;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-dropbox{background-color:#4280ff;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-etsy{background-color:#f45800;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-facebook{background-color:#0866ff;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-fivehundredpx{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-flickr{background-color:#0461dd;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-foursquare{background-color:#e65678;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-github{background-color:#24292d;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-goodreads{background-color:#eceadd;color:#382110}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-google{background-color:#ea4434;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-gravatar{background-color:#1d4fc4;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-instagram{background-color:#f00075;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-lastfm{background-color:#e21b24;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-linkedin{background-color:#0d66c2;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-mastodon{background-color:#3288d4;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-medium{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-meetup{background-color:#f6405f;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-patreon{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-pinterest{background-color:#e60122;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-pocket{background-color:#ef4155;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-reddit{background-color:#ff4500;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-skype{background-color:#0478d7;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-snapchat{background-color:#fefc00;color:#fff;stroke:#000}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-soundcloud{background-color:#ff5600;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-spotify{background-color:#1bd760;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-telegram{background-color:#2aabee;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-threads{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-tiktok{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-tumblr{background-color:#011835;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-twitch{background-color:#6440a4;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-twitter{background-color:#1da1f2;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-vimeo{background-color:#1eb7ea;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-vk{background-color:#4680c2;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-wordpress{background-color:#3499cd;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-whatsapp{background-color:#25d366;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-x{background-color:#000;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-yelp{background-color:#d32422;color:#fff}:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-youtube{background-color:red;color:#fff}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link{background:none}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link svg{height:1.25em;width:1.25em}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-amazon{color:#f90}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-bandcamp{color:#1ea0c3}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-behance{color:#0757fe}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-bluesky{color:#0a7aff}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-codepen{color:#1e1f26}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-deviantart{color:#02e49b}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-discord{color:#5865f2}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-dribbble{color:#e94c89}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-dropbox{color:#4280ff}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-etsy{color:#f45800}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-facebook{color:#0866ff}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-fivehundredpx{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-flickr{color:#0461dd}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-foursquare{color:#e65678}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-github{color:#24292d}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-goodreads{color:#382110}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-google{color:#ea4434}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-gravatar{color:#1d4fc4}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-instagram{color:#f00075}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-lastfm{color:#e21b24}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-linkedin{color:#0d66c2}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-mastodon{color:#3288d4}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-medium{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-meetup{color:#f6405f}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-patreon{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-pinterest{color:#e60122}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-pocket{color:#ef4155}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-reddit{color:#ff4500}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-skype{color:#0478d7}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-snapchat{color:#fff;stroke:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-soundcloud{color:#ff5600}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-spotify{color:#1bd760}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-telegram{color:#2aabee}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-threads{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-tiktok{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-tumblr{color:#011835}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-twitch{color:#6440a4}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-twitter{color:#1da1f2}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-vimeo{color:#1eb7ea}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-vk{color:#4680c2}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-whatsapp{color:#25d366}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-wordpress{color:#3499cd}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-x{color:#000}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-yelp{color:#d32422}:where(.wp-block-social-links.is-style-logos-only) .wp-social-link-youtube{color:red}.wp-block-social-links.is-style-pill-shape .wp-social-link{width:auto}:root :where(.wp-block-social-links .wp-social-link a){padding:.25em}:root :where(.wp-block-social-links.is-style-logos-only .wp-social-link a){padding:0}:root :where(.wp-block-social-links.is-style-pill-shape .wp-social-link a){padding-left:.6666666667em;padding-right:.6666666667em}.wp-block-social-links:not(.has-icon-color):not(.has-icon-background-color) .wp-social-link-snapchat .wp-block-social-link-label{color:#000}PKt\-social-links/editor-rtl.min.cssnu[.wp-block-social-links div.block-editor-url-input{display:inline-block;margin-right:8px}.wp-social-link:hover{transform:none}:root :where(.wp-block-social-links),:root :where(.wp-block-social-links.is-style-logos-only .wp-block-social-links__social-placeholder .wp-social-link){padding:0}:root :where(.wp-block-social-links__social-placeholder .wp-social-link){padding:.25em}:root :where(.wp-block-social-links.is-style-pill-shape .wp-block-social-links__social-placeholder .wp-social-link){padding-left:.6666666667em;padding-right:.6666666667em}.wp-block-social-links__social-placeholder{display:flex;list-style:none;opacity:.8}.wp-block-social-links__social-placeholder>.wp-social-link{margin-left:0!important;margin-right:0!important;padding-left:0!important;padding-right:0!important;visibility:hidden;width:0!important}.wp-block-social-links__social-placeholder>.wp-block-social-links__social-placeholder-icons{display:flex}.wp-block-social-links__social-placeholder .wp-social-link:before{border-radius:50%;content:"";display:block;height:1em;width:1em}.is-style-logos-only .wp-block-social-links__social-placeholder .wp-social-link:before{background:currentColor}.wp-block-social-links .wp-block-social-links__social-prompt{cursor:default;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;line-height:24px;list-style:none;margin-bottom:auto;margin-top:auto;min-height:24px;padding-left:8px}.wp-block.wp-block-social-links.aligncenter,.wp-block[data-align=center]>.wp-block-social-links{justify-content:center}.block-editor-block-preview__content .components-button:disabled{opacity:1}.wp-social-link.wp-social-link__is-incomplete{opacity:.5}.wp-block-social-links .is-selected .wp-social-link__is-incomplete,.wp-social-link.wp-social-link__is-incomplete:focus,.wp-social-link.wp-social-link__is-incomplete:hover{opacity:1}.wp-block-social-links .block-list-appender{position:static}.wp-block-social-links .block-list-appender .block-editor-inserter{font-size:inherit}.wp-block-social-links .block-list-appender .block-editor-button-block-appender{font-size:inherit;height:1.5em;padding:0;width:1.5em}PKt\:RO O social-links/editor.cssnu[.wp-block-social-links div.block-editor-url-input{ display:inline-block; margin-left:8px; } .wp-social-link:hover{ transform:none; } :root :where(.wp-block-social-links),:root :where(.wp-block-social-links.is-style-logos-only .wp-block-social-links__social-placeholder .wp-social-link){ padding:0; } :root :where(.wp-block-social-links__social-placeholder .wp-social-link){ padding:.25em; } :root :where(.wp-block-social-links.is-style-pill-shape .wp-block-social-links__social-placeholder .wp-social-link){ padding-left:.6666666667em; padding-right:.6666666667em; } .wp-block-social-links__social-placeholder{ display:flex; list-style:none; opacity:.8; } .wp-block-social-links__social-placeholder>.wp-social-link{ margin-left:0 !important; margin-right:0 !important; padding-left:0 !important; padding-right:0 !important; visibility:hidden; width:0 !important; } .wp-block-social-links__social-placeholder>.wp-block-social-links__social-placeholder-icons{ display:flex; } .wp-block-social-links__social-placeholder .wp-social-link:before{ border-radius:50%; content:""; display:block; height:1em; width:1em; } .is-style-logos-only .wp-block-social-links__social-placeholder .wp-social-link:before{ background:currentColor; } .wp-block-social-links .wp-block-social-links__social-prompt{ cursor:default; font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif; font-size:13px; line-height:24px; list-style:none; margin-bottom:auto; margin-top:auto; min-height:24px; padding-right:8px; } .wp-block.wp-block-social-links.aligncenter,.wp-block[data-align=center]>.wp-block-social-links{ justify-content:center; } .block-editor-block-preview__content .components-button:disabled{ opacity:1; } .wp-social-link.wp-social-link__is-incomplete{ opacity:.5; } .wp-block-social-links .is-selected .wp-social-link__is-incomplete,.wp-social-link.wp-social-link__is-incomplete:focus,.wp-social-link.wp-social-link__is-incomplete:hover{ opacity:1; } .wp-block-social-links .block-list-appender{ position:static; } .wp-block-social-links .block-list-appender .block-editor-inserter{ font-size:inherit; } .wp-block-social-links .block-list-appender .block-editor-button-block-appender{ font-size:inherit; height:1.5em; padding:0; width:1.5em; }PKt\i}ddcolumn/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/column", "title": "Column", "category": "design", "parent": [ "core/columns" ], "description": "A single column within a columns block.", "textdomain": "default", "attributes": { "verticalAlignment": { "type": "string" }, "width": { "type": "string" }, "allowedBlocks": { "type": "array" }, "templateLock": { "type": [ "string", "boolean" ], "enum": [ "all", "insert", "contentOnly", false ] } }, "supports": { "__experimentalOnEnter": true, "anchor": true, "reusable": false, "html": false, "color": { "gradients": true, "heading": true, "button": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "shadow": true, "spacing": { "blockGap": true, "padding": true, "__experimentalDefaultControls": { "padding": true, "blockGap": true } }, "__experimentalBorder": { "color": true, "radius": true, "style": true, "width": true, "__experimentalDefaultControls": { "color": true, "radius": true, "style": true, "width": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "layout": true, "interactivity": { "clientNavigation": true } } } PKt\_comment-template.phpnu[comment_ID; $filter_block_context = static function ( $context ) use ( $comment_id ) { $context['commentId'] = $comment_id; return $context; }; /* * We set commentId context through the `render_block_context` filter so * that dynamically inserted blocks (at `render_block` filter stage) * will also receive that context. * * Use an early priority to so that other 'render_block_context' filters * have access to the values. */ add_filter( 'render_block_context', $filter_block_context, 1 ); /* * We construct a new WP_Block instance from the parsed block so that * it'll receive any changes made by the `render_block_data` filter. */ $block_content = ( new WP_Block( $block->parsed_block ) )->render( array( 'dynamic' => false ) ); remove_filter( 'render_block_context', $filter_block_context, 1 ); $children = $comment->get_children(); /* * We need to create the CSS classes BEFORE recursing into the children. * This is because comment_class() uses globals like `$comment_alt` * and `$comment_thread_alt` which are order-sensitive. * * The `false` parameter at the end means that we do NOT want the function * to `echo` the output but to return a string. * See https://developer.wordpress.org/reference/functions/comment_class/#parameters. */ $comment_classes = comment_class( '', $comment->comment_ID, $comment->comment_post_ID, false ); // If the comment has children, recurse to create the HTML for the nested // comments. if ( ! empty( $children ) && ! empty( $thread_comments ) ) { if ( $comment_depth < $thread_comments_depth ) { ++$comment_depth; $inner_content = block_core_comment_template_render_comments( $children, $block ); $block_content .= sprintf( '
            %1$s
          ', $inner_content ); --$comment_depth; } else { $block_content .= block_core_comment_template_render_comments( $children, $block ); } } $content .= sprintf( '
        • %3$s
        • ', $comment->comment_ID, $comment_classes, $block_content ); } return $content; } /** * Renders the `core/comment-template` block on the server. * * @since 6.0.0 * * @param array $attributes Block attributes. * @param string $content Block default content. * @param WP_Block $block Block instance. * * @return string Returns the HTML representing the comments using the layout * defined by the block's inner blocks. */ function render_block_core_comment_template( $attributes, $content, $block ) { // Bail out early if the post ID is not set for some reason. if ( empty( $block->context['postId'] ) ) { return ''; } if ( post_password_required( $block->context['postId'] ) ) { return; } $comment_query = new WP_Comment_Query( build_comment_query_vars_from_block( $block ) ); // Get an array of comments for the current post. $comments = $comment_query->get_comments(); if ( count( $comments ) === 0 ) { return ''; } $comment_order = get_option( 'comment_order' ); if ( 'desc' === $comment_order ) { $comments = array_reverse( $comments ); } $wrapper_attributes = get_block_wrapper_attributes(); return sprintf( '
            %2$s
          ', $wrapper_attributes, block_core_comment_template_render_comments( $comments, $block ) ); } /** * Registers the `core/comment-template` block on the server. * * @since 6.0.0 */ function register_block_core_comment_template() { register_block_type_from_metadata( __DIR__ . '/comment-template', array( 'render_callback' => 'render_block_core_comment_template', 'skip_inner_blocks' => true, ) ); } add_action( 'init', 'register_block_core_comment_template' ); PKt\M##aiipage-list-item.phpnu[Hello World * * Would be transformed to: *

          Hello World

          * * @since 6.2.0 * * @param array $attributes Attributes of the block being rendered. * @param string $content Content of the block being rendered. * * @return string The content of the block being rendered. */ function block_core_heading_render( $attributes, $content ) { if ( ! $content ) { return $content; } $p = new WP_HTML_Tag_Processor( $content ); $header_tags = array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ); while ( $p->next_tag() ) { if ( in_array( $p->get_tag(), $header_tags, true ) ) { $p->add_class( 'wp-block-heading' ); break; } } return $p->get_updated_html(); } /** * Registers the `core/heading` block on server. * * @since 6.2.0 */ function register_block_core_heading() { register_block_type_from_metadata( __DIR__ . '/heading', array( 'render_callback' => 'block_core_heading_render', ) ); } add_action( 'init', 'register_block_core_heading' ); PKt\l66buttons/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/buttons", "title": "Buttons", "category": "design", "allowedBlocks": [ "core/button" ], "description": "Prompt visitors to take action with a group of button-style links.", "keywords": [ "link" ], "textdomain": "default", "supports": { "anchor": true, "align": [ "wide", "full" ], "html": false, "__experimentalExposeControlsToChildren": true, "color": { "gradients": true, "text": false, "__experimentalDefaultControls": { "background": true } }, "spacing": { "blockGap": [ "horizontal", "vertical" ], "padding": true, "margin": [ "top", "bottom" ], "__experimentalDefaultControls": { "blockGap": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "__experimentalBorder": { "color": true, "radius": true, "style": true, "width": true, "__experimentalDefaultControls": { "color": true, "radius": true, "style": true, "width": true } }, "layout": { "allowSwitching": false, "allowInheriting": false, "default": { "type": "flex" } }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-buttons-editor", "style": "wp-block-buttons" } PKt\mbuttons/editor.min.cssnu[.wp-block-buttons>.wp-block,.wp-block-buttons>.wp-block-button.wp-block-button.wp-block-button.wp-block-button.wp-block-button{margin:0}.wp-block-buttons>.block-list-appender{align-items:center;display:inline-flex}.wp-block-buttons.is-vertical>.block-list-appender .block-list-appender__toggle{justify-content:flex-start}.wp-block-buttons>.wp-block-button:focus{box-shadow:none}.wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block[data-align=center]{margin-left:auto;margin-right:auto;margin-top:0;width:100%}.wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block[data-align=center] .wp-block-button{margin-bottom:0}.wp-block[data-align=center]>.wp-block-buttons{align-items:center;justify-content:center}.wp-block[data-align=right]>.wp-block-buttons{justify-content:flex-end}PKt\0&ssbuttons/style.min.cssnu[.wp-block-buttons{box-sizing:border-box}.wp-block-buttons.is-vertical{flex-direction:column}.wp-block-buttons.is-vertical>.wp-block-button:last-child{margin-bottom:0}.wp-block-buttons>.wp-block-button{display:inline-block;margin:0}.wp-block-buttons.is-content-justification-left{justify-content:flex-start}.wp-block-buttons.is-content-justification-left.is-vertical{align-items:flex-start}.wp-block-buttons.is-content-justification-center{justify-content:center}.wp-block-buttons.is-content-justification-center.is-vertical{align-items:center}.wp-block-buttons.is-content-justification-right{justify-content:flex-end}.wp-block-buttons.is-content-justification-right.is-vertical{align-items:flex-end}.wp-block-buttons.is-content-justification-space-between{justify-content:space-between}.wp-block-buttons.aligncenter{text-align:center}.wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block-button.aligncenter{margin-left:auto;margin-right:auto;width:100%}.wp-block-buttons[style*=text-decoration] .wp-block-button,.wp-block-buttons[style*=text-decoration] .wp-block-button__link{text-decoration:inherit}.wp-block-buttons.has-custom-font-size .wp-block-button__link{font-size:inherit}.wp-block-buttons .wp-block-button__link{width:100%}.wp-block-button.aligncenter{text-align:center}PKt\kbuttons/style-rtl.cssnu[.wp-block-buttons{ box-sizing:border-box; } .wp-block-buttons.is-vertical{ flex-direction:column; } .wp-block-buttons.is-vertical>.wp-block-button:last-child{ margin-bottom:0; } .wp-block-buttons>.wp-block-button{ display:inline-block; margin:0; } .wp-block-buttons.is-content-justification-left{ justify-content:flex-start; } .wp-block-buttons.is-content-justification-left.is-vertical{ align-items:flex-start; } .wp-block-buttons.is-content-justification-center{ justify-content:center; } .wp-block-buttons.is-content-justification-center.is-vertical{ align-items:center; } .wp-block-buttons.is-content-justification-right{ justify-content:flex-end; } .wp-block-buttons.is-content-justification-right.is-vertical{ align-items:flex-end; } .wp-block-buttons.is-content-justification-space-between{ justify-content:space-between; } .wp-block-buttons.aligncenter{ text-align:center; } .wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block-button.aligncenter{ margin-left:auto; margin-right:auto; width:100%; } .wp-block-buttons[style*=text-decoration] .wp-block-button,.wp-block-buttons[style*=text-decoration] .wp-block-button__link{ text-decoration:inherit; } .wp-block-buttons.has-custom-font-size .wp-block-button__link{ font-size:inherit; } .wp-block-buttons .wp-block-button__link{ width:100%; } .wp-block-button.aligncenter{ text-align:center; }PKt\Zl..buttons/editor-rtl.cssnu[.wp-block-buttons>.wp-block,.wp-block-buttons>.wp-block-button.wp-block-button.wp-block-button.wp-block-button.wp-block-button{ margin:0; } .wp-block-buttons>.block-list-appender{ align-items:center; display:inline-flex; } .wp-block-buttons.is-vertical>.block-list-appender .block-list-appender__toggle{ justify-content:flex-start; } .wp-block-buttons>.wp-block-button:focus{ box-shadow:none; } .wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block[data-align=center]{ margin-left:auto; margin-right:auto; margin-top:0; width:100%; } .wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block[data-align=center] .wp-block-button{ margin-bottom:0; } .wp-block[data-align=center]>.wp-block-buttons{ align-items:center; justify-content:center; } .wp-block[data-align=right]>.wp-block-buttons{ justify-content:flex-end; }PKt\kbuttons/style.cssnu[.wp-block-buttons{ box-sizing:border-box; } .wp-block-buttons.is-vertical{ flex-direction:column; } .wp-block-buttons.is-vertical>.wp-block-button:last-child{ margin-bottom:0; } .wp-block-buttons>.wp-block-button{ display:inline-block; margin:0; } .wp-block-buttons.is-content-justification-left{ justify-content:flex-start; } .wp-block-buttons.is-content-justification-left.is-vertical{ align-items:flex-start; } .wp-block-buttons.is-content-justification-center{ justify-content:center; } .wp-block-buttons.is-content-justification-center.is-vertical{ align-items:center; } .wp-block-buttons.is-content-justification-right{ justify-content:flex-end; } .wp-block-buttons.is-content-justification-right.is-vertical{ align-items:flex-end; } .wp-block-buttons.is-content-justification-space-between{ justify-content:space-between; } .wp-block-buttons.aligncenter{ text-align:center; } .wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block-button.aligncenter{ margin-left:auto; margin-right:auto; width:100%; } .wp-block-buttons[style*=text-decoration] .wp-block-button,.wp-block-buttons[style*=text-decoration] .wp-block-button__link{ text-decoration:inherit; } .wp-block-buttons.has-custom-font-size .wp-block-button__link{ font-size:inherit; } .wp-block-buttons .wp-block-button__link{ width:100%; } .wp-block-button.aligncenter{ text-align:center; }PKt\0&ssbuttons/style-rtl.min.cssnu[.wp-block-buttons{box-sizing:border-box}.wp-block-buttons.is-vertical{flex-direction:column}.wp-block-buttons.is-vertical>.wp-block-button:last-child{margin-bottom:0}.wp-block-buttons>.wp-block-button{display:inline-block;margin:0}.wp-block-buttons.is-content-justification-left{justify-content:flex-start}.wp-block-buttons.is-content-justification-left.is-vertical{align-items:flex-start}.wp-block-buttons.is-content-justification-center{justify-content:center}.wp-block-buttons.is-content-justification-center.is-vertical{align-items:center}.wp-block-buttons.is-content-justification-right{justify-content:flex-end}.wp-block-buttons.is-content-justification-right.is-vertical{align-items:flex-end}.wp-block-buttons.is-content-justification-space-between{justify-content:space-between}.wp-block-buttons.aligncenter{text-align:center}.wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block-button.aligncenter{margin-left:auto;margin-right:auto;width:100%}.wp-block-buttons[style*=text-decoration] .wp-block-button,.wp-block-buttons[style*=text-decoration] .wp-block-button__link{text-decoration:inherit}.wp-block-buttons.has-custom-font-size .wp-block-button__link{font-size:inherit}.wp-block-buttons .wp-block-button__link{width:100%}.wp-block-button.aligncenter{text-align:center}PKt\mbuttons/editor-rtl.min.cssnu[.wp-block-buttons>.wp-block,.wp-block-buttons>.wp-block-button.wp-block-button.wp-block-button.wp-block-button.wp-block-button{margin:0}.wp-block-buttons>.block-list-appender{align-items:center;display:inline-flex}.wp-block-buttons.is-vertical>.block-list-appender .block-list-appender__toggle{justify-content:flex-start}.wp-block-buttons>.wp-block-button:focus{box-shadow:none}.wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block[data-align=center]{margin-left:auto;margin-right:auto;margin-top:0;width:100%}.wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block[data-align=center] .wp-block-button{margin-bottom:0}.wp-block[data-align=center]>.wp-block-buttons{align-items:center;justify-content:center}.wp-block[data-align=right]>.wp-block-buttons{justify-content:flex-end}PKt\Zl..buttons/editor.cssnu[.wp-block-buttons>.wp-block,.wp-block-buttons>.wp-block-button.wp-block-button.wp-block-button.wp-block-button.wp-block-button{ margin:0; } .wp-block-buttons>.block-list-appender{ align-items:center; display:inline-flex; } .wp-block-buttons.is-vertical>.block-list-appender .block-list-appender__toggle{ justify-content:flex-start; } .wp-block-buttons>.wp-block-button:focus{ box-shadow:none; } .wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block[data-align=center]{ margin-left:auto; margin-right:auto; margin-top:0; width:100%; } .wp-block-buttons:not(.is-content-justification-space-between,.is-content-justification-right,.is-content-justification-left,.is-content-justification-center) .wp-block[data-align=center] .wp-block-button{ margin-bottom:0; } .wp-block[data-align=center]>.wp-block-buttons{ align-items:center; justify-content:center; } .wp-block[data-align=right]>.wp-block-buttons{ justify-content:flex-end; }PKt\##post-template/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/post-template", "title": "Post Template", "category": "theme", "ancestor": [ "core/query" ], "description": "Contains the block elements used to render a post, like the title, date, featured image, content or excerpt, and more.", "textdomain": "default", "usesContext": [ "queryId", "query", "displayLayout", "templateSlug", "previewPostType", "enhancedPagination", "postType" ], "supports": { "reusable": false, "html": false, "align": [ "wide", "full" ], "layout": true, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "spacing": { "margin": true, "padding": true, "blockGap": { "__experimentalDefault": "1.25em" }, "__experimentalDefaultControls": { "blockGap": true, "padding": false, "margin": false } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true } }, "style": "wp-block-post-template", "editorStyle": "wp-block-post-template-editor" } PKt\post-template/style.min.cssnu[.wp-block-post-template{box-sizing:border-box;list-style:none;margin-bottom:0;margin-top:0;max-width:100%;padding:0}.wp-block-post-template.is-flex-container{display:flex;flex-direction:row;flex-wrap:wrap;gap:1.25em}.wp-block-post-template.is-flex-container>li{margin:0;width:100%}@media (min-width:600px){.wp-block-post-template.is-flex-container.is-flex-container.columns-2>li{width:calc(50% - .625em)}.wp-block-post-template.is-flex-container.is-flex-container.columns-3>li{width:calc(33.33333% - .83333em)}.wp-block-post-template.is-flex-container.is-flex-container.columns-4>li{width:calc(25% - .9375em)}.wp-block-post-template.is-flex-container.is-flex-container.columns-5>li{width:calc(20% - 1em)}.wp-block-post-template.is-flex-container.is-flex-container.columns-6>li{width:calc(16.66667% - 1.04167em)}}@media (max-width:600px){.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid{grid-template-columns:1fr}}.wp-block-post-template-is-layout-constrained>li>.alignright,.wp-block-post-template-is-layout-flow>li>.alignright{float:right;margin-inline-end:0;margin-inline-start:2em}.wp-block-post-template-is-layout-constrained>li>.alignleft,.wp-block-post-template-is-layout-flow>li>.alignleft{float:left;margin-inline-end:2em;margin-inline-start:0}.wp-block-post-template-is-layout-constrained>li>.aligncenter,.wp-block-post-template-is-layout-flow>li>.aligncenter{margin-inline-end:auto;margin-inline-start:auto}PKt\ post-template/style-rtl.cssnu[.wp-block-post-template{ box-sizing:border-box; list-style:none; margin-bottom:0; margin-top:0; max-width:100%; padding:0; } .wp-block-post-template.is-flex-container{ display:flex; flex-direction:row; flex-wrap:wrap; gap:1.25em; } .wp-block-post-template.is-flex-container>li{ margin:0; width:100%; } @media (min-width:600px){ .wp-block-post-template.is-flex-container.is-flex-container.columns-2>li{ width:calc(50% - .625em); } .wp-block-post-template.is-flex-container.is-flex-container.columns-3>li{ width:calc(33.33333% - .83333em); } .wp-block-post-template.is-flex-container.is-flex-container.columns-4>li{ width:calc(25% - .9375em); } .wp-block-post-template.is-flex-container.is-flex-container.columns-5>li{ width:calc(20% - 1em); } .wp-block-post-template.is-flex-container.is-flex-container.columns-6>li{ width:calc(16.66667% - 1.04167em); } } @media (max-width:600px){ .wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid{ grid-template-columns:1fr; } } .wp-block-post-template-is-layout-constrained>li>.alignright,.wp-block-post-template-is-layout-flow>li>.alignright{ float:left; margin-inline-end:0; margin-inline-start:2em; } .wp-block-post-template-is-layout-constrained>li>.alignleft,.wp-block-post-template-is-layout-flow>li>.alignleft{ float:right; margin-inline-end:2em; margin-inline-start:0; } .wp-block-post-template-is-layout-constrained>li>.aligncenter,.wp-block-post-template-is-layout-flow>li>.aligncenter{ margin-inline-end:auto; margin-inline-start:auto; }PKt\dPpost-template/style.cssnu[.wp-block-post-template{ box-sizing:border-box; list-style:none; margin-bottom:0; margin-top:0; max-width:100%; padding:0; } .wp-block-post-template.is-flex-container{ display:flex; flex-direction:row; flex-wrap:wrap; gap:1.25em; } .wp-block-post-template.is-flex-container>li{ margin:0; width:100%; } @media (min-width:600px){ .wp-block-post-template.is-flex-container.is-flex-container.columns-2>li{ width:calc(50% - .625em); } .wp-block-post-template.is-flex-container.is-flex-container.columns-3>li{ width:calc(33.33333% - .83333em); } .wp-block-post-template.is-flex-container.is-flex-container.columns-4>li{ width:calc(25% - .9375em); } .wp-block-post-template.is-flex-container.is-flex-container.columns-5>li{ width:calc(20% - 1em); } .wp-block-post-template.is-flex-container.is-flex-container.columns-6>li{ width:calc(16.66667% - 1.04167em); } } @media (max-width:600px){ .wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid{ grid-template-columns:1fr; } } .wp-block-post-template-is-layout-constrained>li>.alignright,.wp-block-post-template-is-layout-flow>li>.alignright{ float:right; margin-inline-end:0; margin-inline-start:2em; } .wp-block-post-template-is-layout-constrained>li>.alignleft,.wp-block-post-template-is-layout-flow>li>.alignleft{ float:left; margin-inline-end:2em; margin-inline-start:0; } .wp-block-post-template-is-layout-constrained>li>.aligncenter,.wp-block-post-template-is-layout-flow>li>.aligncenter{ margin-inline-end:auto; margin-inline-start:auto; }PKt\|*post-template/style-rtl.min.cssnu[.wp-block-post-template{box-sizing:border-box;list-style:none;margin-bottom:0;margin-top:0;max-width:100%;padding:0}.wp-block-post-template.is-flex-container{display:flex;flex-direction:row;flex-wrap:wrap;gap:1.25em}.wp-block-post-template.is-flex-container>li{margin:0;width:100%}@media (min-width:600px){.wp-block-post-template.is-flex-container.is-flex-container.columns-2>li{width:calc(50% - .625em)}.wp-block-post-template.is-flex-container.is-flex-container.columns-3>li{width:calc(33.33333% - .83333em)}.wp-block-post-template.is-flex-container.is-flex-container.columns-4>li{width:calc(25% - .9375em)}.wp-block-post-template.is-flex-container.is-flex-container.columns-5>li{width:calc(20% - 1em)}.wp-block-post-template.is-flex-container.is-flex-container.columns-6>li{width:calc(16.66667% - 1.04167em)}}@media (max-width:600px){.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid{grid-template-columns:1fr}}.wp-block-post-template-is-layout-constrained>li>.alignright,.wp-block-post-template-is-layout-flow>li>.alignright{float:left;margin-inline-end:0;margin-inline-start:2em}.wp-block-post-template-is-layout-constrained>li>.alignleft,.wp-block-post-template-is-layout-flow>li>.alignleft{float:right;margin-inline-end:2em;margin-inline-start:0}.wp-block-post-template-is-layout-constrained>li>.aligncenter,.wp-block-post-template-is-layout-flow>li>.aligncenter{margin-inline-end:auto;margin-inline-start:auto}PKt\qMblocks-json.phpnu[ array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/archives', 'title' => 'Archives', 'category' => 'widgets', 'description' => 'Display a date archive of your posts.', 'textdomain' => 'default', 'attributes' => array( 'displayAsDropdown' => array( 'type' => 'boolean', 'default' => false ), 'showLabel' => array( 'type' => 'boolean', 'default' => true ), 'showPostCounts' => array( 'type' => 'boolean', 'default' => false ), 'type' => array( 'type' => 'string', 'default' => 'monthly' ) ), 'supports' => array( 'align' => true, '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ), 'html' => false, 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-archives-editor' ), 'audio' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/audio', 'title' => 'Audio', 'category' => 'media', 'description' => 'Embed a simple audio player.', 'keywords' => array( 'music', 'sound', 'podcast', 'recording' ), 'textdomain' => 'default', 'attributes' => array( 'blob' => array( 'type' => 'string', 'role' => 'local' ), 'src' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'audio', 'attribute' => 'src', 'role' => 'content' ), 'caption' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'figcaption', 'role' => 'content' ), 'id' => array( 'type' => 'number', 'role' => 'content' ), 'autoplay' => array( 'type' => 'boolean', 'source' => 'attribute', 'selector' => 'audio', 'attribute' => 'autoplay' ), 'loop' => array( 'type' => 'boolean', 'source' => 'attribute', 'selector' => 'audio', 'attribute' => 'loop' ), 'preload' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'audio', 'attribute' => 'preload' ) ), 'supports' => array( 'anchor' => true, 'align' => true, 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-audio-editor', 'style' => 'wp-block-audio' ), 'avatar' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/avatar', 'title' => 'Avatar', 'category' => 'theme', 'description' => 'Add a user’s avatar.', 'textdomain' => 'default', 'attributes' => array( 'userId' => array( 'type' => 'number' ), 'size' => array( 'type' => 'number', 'default' => 96 ), 'isLink' => array( 'type' => 'boolean', 'default' => false ), 'linkTarget' => array( 'type' => 'string', 'default' => '_self' ) ), 'usesContext' => array( 'postType', 'postId', 'commentId' ), 'supports' => array( 'html' => false, 'align' => true, 'alignWide' => false, 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), '__experimentalBorder' => array( '__experimentalSkipSerialization' => true, 'radius' => true, 'width' => true, 'color' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true ) ), 'color' => array( 'text' => false, 'background' => false, '__experimentalDuotone' => 'img' ), 'interactivity' => array( 'clientNavigation' => true ) ), 'selectors' => array( 'border' => '.wp-block-avatar img' ), 'editorStyle' => 'wp-block-avatar-editor', 'style' => 'wp-block-avatar' ), 'block' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/block', 'title' => 'Pattern', 'category' => 'reusable', 'description' => 'Reuse this design across your site.', 'keywords' => array( 'reusable' ), 'textdomain' => 'default', 'attributes' => array( 'ref' => array( 'type' => 'number' ), 'content' => array( 'type' => 'object', 'default' => array( ) ) ), 'providesContext' => array( 'pattern/overrides' => 'content' ), 'supports' => array( 'customClassName' => false, 'html' => false, 'inserter' => false, 'renaming' => false, 'interactivity' => array( 'clientNavigation' => true ) ) ), 'button' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/button', 'title' => 'Button', 'category' => 'design', 'parent' => array( 'core/buttons' ), 'description' => 'Prompt visitors to take action with a button-style link.', 'keywords' => array( 'link' ), 'textdomain' => 'default', 'attributes' => array( 'tagName' => array( 'type' => 'string', 'enum' => array( 'a', 'button' ), 'default' => 'a' ), 'type' => array( 'type' => 'string', 'default' => 'button' ), 'textAlign' => array( 'type' => 'string' ), 'url' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'a', 'attribute' => 'href', 'role' => 'content' ), 'title' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'a,button', 'attribute' => 'title', 'role' => 'content' ), 'text' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'a,button', 'role' => 'content' ), 'linkTarget' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'a', 'attribute' => 'target', 'role' => 'content' ), 'rel' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'a', 'attribute' => 'rel', 'role' => 'content' ), 'placeholder' => array( 'type' => 'string' ), 'backgroundColor' => array( 'type' => 'string' ), 'textColor' => array( 'type' => 'string' ), 'gradient' => array( 'type' => 'string' ), 'width' => array( 'type' => 'number' ) ), 'supports' => array( 'anchor' => true, 'splitting' => true, 'align' => false, 'alignWide' => false, 'color' => array( '__experimentalSkipSerialization' => true, 'gradients' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'typography' => array( '__experimentalSkipSerialization' => array( 'fontSize', 'lineHeight', 'fontFamily', 'fontWeight', 'fontStyle', 'textTransform', 'textDecoration', 'letterSpacing' ), 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalWritingMode' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'reusable' => false, 'shadow' => array( '__experimentalSkipSerialization' => true ), 'spacing' => array( '__experimentalSkipSerialization' => true, 'padding' => array( 'horizontal', 'vertical' ), '__experimentalDefaultControls' => array( 'padding' => true ) ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true, '__experimentalSkipSerialization' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'styles' => array( array( 'name' => 'fill', 'label' => 'Fill', 'isDefault' => true ), array( 'name' => 'outline', 'label' => 'Outline' ) ), 'editorStyle' => 'wp-block-button-editor', 'style' => 'wp-block-button', 'selectors' => array( 'root' => '.wp-block-button .wp-block-button__link', 'typography' => array( 'writingMode' => '.wp-block-button' ) ) ), 'buttons' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/buttons', 'title' => 'Buttons', 'category' => 'design', 'allowedBlocks' => array( 'core/button' ), 'description' => 'Prompt visitors to take action with a group of button-style links.', 'keywords' => array( 'link' ), 'textdomain' => 'default', 'supports' => array( 'anchor' => true, 'align' => array( 'wide', 'full' ), 'html' => false, '__experimentalExposeControlsToChildren' => true, 'color' => array( 'gradients' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true ) ), 'spacing' => array( 'blockGap' => array( 'horizontal', 'vertical' ), 'padding' => true, 'margin' => array( 'top', 'bottom' ), '__experimentalDefaultControls' => array( 'blockGap' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ) ), 'layout' => array( 'allowSwitching' => false, 'allowInheriting' => false, 'default' => array( 'type' => 'flex' ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-buttons-editor', 'style' => 'wp-block-buttons' ), 'calendar' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/calendar', 'title' => 'Calendar', 'category' => 'widgets', 'description' => 'A calendar of your site’s posts.', 'keywords' => array( 'posts', 'archive' ), 'textdomain' => 'default', 'attributes' => array( 'month' => array( 'type' => 'integer' ), 'year' => array( 'type' => 'integer' ) ), 'supports' => array( 'align' => true, 'color' => array( 'link' => true, '__experimentalSkipSerialization' => array( 'text', 'background' ), '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ), '__experimentalSelector' => 'table, th' ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'style' => 'wp-block-calendar' ), 'categories' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/categories', 'title' => 'Terms List', 'category' => 'widgets', 'description' => 'Display a list of all terms of a given taxonomy.', 'keywords' => array( 'categories' ), 'textdomain' => 'default', 'attributes' => array( 'taxonomy' => array( 'type' => 'string', 'default' => 'category' ), 'displayAsDropdown' => array( 'type' => 'boolean', 'default' => false ), 'showHierarchy' => array( 'type' => 'boolean', 'default' => false ), 'showPostCounts' => array( 'type' => 'boolean', 'default' => false ), 'showOnlyTopLevel' => array( 'type' => 'boolean', 'default' => false ), 'showEmpty' => array( 'type' => 'boolean', 'default' => false ), 'label' => array( 'type' => 'string', 'role' => 'content' ), 'showLabel' => array( 'type' => 'boolean', 'default' => true ) ), 'usesContext' => array( 'enhancedPagination' ), 'supports' => array( 'align' => true, 'html' => false, 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'editorStyle' => 'wp-block-categories-editor', 'style' => 'wp-block-categories' ), 'code' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/code', 'title' => 'Code', 'category' => 'text', 'description' => 'Display code snippets that respect your spacing and tabs.', 'textdomain' => 'default', 'attributes' => array( 'content' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'code', '__unstablePreserveWhiteSpace' => true ) ), 'supports' => array( 'align' => array( 'wide' ), 'anchor' => true, 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'spacing' => array( 'margin' => array( 'top', 'bottom' ), 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'width' => true, 'color' => true ) ), 'color' => array( 'text' => true, 'background' => true, 'gradients' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'style' => 'wp-block-code' ), 'column' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/column', 'title' => 'Column', 'category' => 'design', 'parent' => array( 'core/columns' ), 'description' => 'A single column within a columns block.', 'textdomain' => 'default', 'attributes' => array( 'verticalAlignment' => array( 'type' => 'string' ), 'width' => array( 'type' => 'string' ), 'allowedBlocks' => array( 'type' => 'array' ), 'templateLock' => array( 'type' => array( 'string', 'boolean' ), 'enum' => array( 'all', 'insert', 'contentOnly', false ) ) ), 'supports' => array( '__experimentalOnEnter' => true, 'anchor' => true, 'reusable' => false, 'html' => false, 'color' => array( 'gradients' => true, 'heading' => true, 'button' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'shadow' => true, 'spacing' => array( 'blockGap' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'padding' => true, 'blockGap' => true ) ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'layout' => true, 'interactivity' => array( 'clientNavigation' => true ) ) ), 'columns' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/columns', 'title' => 'Columns', 'category' => 'design', 'allowedBlocks' => array( 'core/column' ), 'description' => 'Display content in multiple columns, with blocks added to each column.', 'textdomain' => 'default', 'attributes' => array( 'verticalAlignment' => array( 'type' => 'string' ), 'isStackedOnMobile' => array( 'type' => 'boolean', 'default' => true ), 'templateLock' => array( 'type' => array( 'string', 'boolean' ), 'enum' => array( 'all', 'insert', 'contentOnly', false ) ) ), 'supports' => array( 'anchor' => true, 'align' => array( 'wide', 'full' ), 'html' => false, 'color' => array( 'gradients' => true, 'link' => true, 'heading' => true, 'button' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'blockGap' => array( '__experimentalDefault' => '2em', 'sides' => array( 'horizontal', 'vertical' ) ), 'margin' => array( 'top', 'bottom' ), 'padding' => true, '__experimentalDefaultControls' => array( 'padding' => true, 'blockGap' => true ) ), 'layout' => array( 'allowSwitching' => false, 'allowInheriting' => false, 'allowEditing' => false, 'default' => array( 'type' => 'flex', 'flexWrap' => 'nowrap' ) ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), 'shadow' => true ), 'editorStyle' => 'wp-block-columns-editor', 'style' => 'wp-block-columns' ), 'comment-author-name' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comment-author-name', 'title' => 'Comment Author Name', 'category' => 'theme', 'ancestor' => array( 'core/comment-template' ), 'description' => 'Displays the name of the author of the comment.', 'textdomain' => 'default', 'attributes' => array( 'isLink' => array( 'type' => 'boolean', 'default' => true ), 'linkTarget' => array( 'type' => 'string', 'default' => '_self' ), 'textAlign' => array( 'type' => 'string' ) ), 'usesContext' => array( 'commentId' ), 'supports' => array( 'html' => false, 'spacing' => array( 'margin' => true, 'padding' => true ), 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'style' => 'wp-block-comment-author-name' ), 'comment-content' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comment-content', 'title' => 'Comment Content', 'category' => 'theme', 'ancestor' => array( 'core/comment-template' ), 'description' => 'Displays the contents of a comment.', 'textdomain' => 'default', 'usesContext' => array( 'commentId' ), 'attributes' => array( 'textAlign' => array( 'type' => 'string' ) ), 'supports' => array( 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ), 'spacing' => array( 'padding' => array( 'horizontal', 'vertical' ), '__experimentalDefaultControls' => array( 'padding' => true ) ), 'html' => false ), 'style' => 'wp-block-comment-content' ), 'comment-date' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comment-date', 'title' => 'Comment Date', 'category' => 'theme', 'ancestor' => array( 'core/comment-template' ), 'description' => 'Displays the date on which the comment was posted.', 'textdomain' => 'default', 'attributes' => array( 'format' => array( 'type' => 'string' ), 'isLink' => array( 'type' => 'boolean', 'default' => true ) ), 'usesContext' => array( 'commentId' ), 'supports' => array( 'html' => false, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'style' => 'wp-block-comment-date' ), 'comment-edit-link' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comment-edit-link', 'title' => 'Comment Edit Link', 'category' => 'theme', 'ancestor' => array( 'core/comment-template' ), 'description' => 'Displays a link to edit the comment in the WordPress Dashboard. This link is only visible to users with the edit comment capability.', 'textdomain' => 'default', 'usesContext' => array( 'commentId' ), 'attributes' => array( 'linkTarget' => array( 'type' => 'string', 'default' => '_self' ), 'textAlign' => array( 'type' => 'string' ) ), 'supports' => array( 'html' => false, 'color' => array( 'link' => true, 'gradients' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true, 'link' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ), 'style' => 'wp-block-comment-edit-link' ), 'comment-reply-link' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comment-reply-link', 'title' => 'Comment Reply Link', 'category' => 'theme', 'ancestor' => array( 'core/comment-template' ), 'description' => 'Displays a link to reply to a comment.', 'textdomain' => 'default', 'usesContext' => array( 'commentId' ), 'attributes' => array( 'textAlign' => array( 'type' => 'string' ) ), 'supports' => array( 'color' => array( 'gradients' => true, 'link' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true, 'link' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ), 'html' => false ), 'style' => 'wp-block-comment-reply-link' ), 'comment-template' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comment-template', 'title' => 'Comment Template', 'category' => 'design', 'parent' => array( 'core/comments' ), 'description' => 'Contains the block elements used to display a comment, like the title, date, author, avatar and more.', 'textdomain' => 'default', 'usesContext' => array( 'postId' ), 'supports' => array( 'align' => true, 'html' => false, 'reusable' => false, 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'style' => 'wp-block-comment-template' ), 'comments' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comments', 'title' => 'Comments', 'category' => 'theme', 'description' => 'An advanced block that allows displaying post comments using different visual configurations.', 'textdomain' => 'default', 'attributes' => array( 'tagName' => array( 'type' => 'string', 'default' => 'div' ), 'legacy' => array( 'type' => 'boolean', 'default' => false ) ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'html' => false, 'color' => array( 'gradients' => true, 'heading' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'editorStyle' => 'wp-block-comments-editor', 'usesContext' => array( 'postId', 'postType' ) ), 'comments-pagination' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comments-pagination', 'title' => 'Comments Pagination', 'category' => 'theme', 'parent' => array( 'core/comments' ), 'allowedBlocks' => array( 'core/comments-pagination-previous', 'core/comments-pagination-numbers', 'core/comments-pagination-next' ), 'description' => 'Displays a paginated navigation to next/previous set of comments, when applicable.', 'textdomain' => 'default', 'attributes' => array( 'paginationArrow' => array( 'type' => 'string', 'default' => 'none' ) ), 'example' => array( 'attributes' => array( 'paginationArrow' => 'none' ) ), 'providesContext' => array( 'comments/paginationArrow' => 'paginationArrow' ), 'supports' => array( 'align' => true, 'reusable' => false, 'html' => false, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'layout' => array( 'allowSwitching' => false, 'allowInheriting' => false, 'default' => array( 'type' => 'flex' ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-comments-pagination-editor', 'style' => 'wp-block-comments-pagination' ), 'comments-pagination-next' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comments-pagination-next', 'title' => 'Comments Next Page', 'category' => 'theme', 'parent' => array( 'core/comments-pagination' ), 'description' => 'Displays the next comment\'s page link.', 'textdomain' => 'default', 'attributes' => array( 'label' => array( 'type' => 'string' ) ), 'usesContext' => array( 'postId', 'comments/paginationArrow' ), 'supports' => array( 'reusable' => false, 'html' => false, 'color' => array( 'gradients' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ) ), 'comments-pagination-numbers' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comments-pagination-numbers', 'title' => 'Comments Page Numbers', 'category' => 'theme', 'parent' => array( 'core/comments-pagination' ), 'description' => 'Displays a list of page numbers for comments pagination.', 'textdomain' => 'default', 'usesContext' => array( 'postId' ), 'supports' => array( 'reusable' => false, 'html' => false, 'color' => array( 'gradients' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ) ), 'comments-pagination-previous' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comments-pagination-previous', 'title' => 'Comments Previous Page', 'category' => 'theme', 'parent' => array( 'core/comments-pagination' ), 'description' => 'Displays the previous comment\'s page link.', 'textdomain' => 'default', 'attributes' => array( 'label' => array( 'type' => 'string' ) ), 'usesContext' => array( 'postId', 'comments/paginationArrow' ), 'supports' => array( 'reusable' => false, 'html' => false, 'color' => array( 'gradients' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ) ), 'comments-title' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/comments-title', 'title' => 'Comments Title', 'category' => 'theme', 'ancestor' => array( 'core/comments' ), 'description' => 'Displays a title with the number of comments.', 'textdomain' => 'default', 'usesContext' => array( 'postId', 'postType' ), 'attributes' => array( 'textAlign' => array( 'type' => 'string' ), 'showPostTitle' => array( 'type' => 'boolean', 'default' => true ), 'showCommentsCount' => array( 'type' => 'boolean', 'default' => true ), 'level' => array( 'type' => 'number', 'default' => 2 ), 'levelOptions' => array( 'type' => 'array' ) ), 'supports' => array( 'anchor' => false, 'align' => true, 'html' => false, '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ), 'color' => array( 'gradients' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true, '__experimentalFontFamily' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ) ), 'cover' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/cover', 'title' => 'Cover', 'category' => 'media', 'description' => 'Add an image or video with a text overlay.', 'textdomain' => 'default', 'attributes' => array( 'url' => array( 'type' => 'string' ), 'useFeaturedImage' => array( 'type' => 'boolean', 'default' => false ), 'id' => array( 'type' => 'number' ), 'alt' => array( 'type' => 'string', 'default' => '' ), 'hasParallax' => array( 'type' => 'boolean', 'default' => false ), 'isRepeated' => array( 'type' => 'boolean', 'default' => false ), 'dimRatio' => array( 'type' => 'number', 'default' => 100 ), 'overlayColor' => array( 'type' => 'string' ), 'customOverlayColor' => array( 'type' => 'string' ), 'isUserOverlayColor' => array( 'type' => 'boolean' ), 'backgroundType' => array( 'type' => 'string', 'default' => 'image' ), 'focalPoint' => array( 'type' => 'object' ), 'minHeight' => array( 'type' => 'number' ), 'minHeightUnit' => array( 'type' => 'string' ), 'gradient' => array( 'type' => 'string' ), 'customGradient' => array( 'type' => 'string' ), 'contentPosition' => array( 'type' => 'string' ), 'isDark' => array( 'type' => 'boolean', 'default' => true ), 'allowedBlocks' => array( 'type' => 'array' ), 'templateLock' => array( 'type' => array( 'string', 'boolean' ), 'enum' => array( 'all', 'insert', 'contentOnly', false ) ), 'tagName' => array( 'type' => 'string', 'default' => 'div' ), 'sizeSlug' => array( 'type' => 'string' ) ), 'usesContext' => array( 'postId', 'postType' ), 'supports' => array( 'anchor' => true, 'align' => true, 'html' => false, 'shadow' => true, 'spacing' => array( 'padding' => true, 'margin' => array( 'top', 'bottom' ), 'blockGap' => true, '__experimentalDefaultControls' => array( 'padding' => true, 'blockGap' => true ) ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ) ), 'color' => array( '__experimentalDuotone' => '> .wp-block-cover__image-background, > .wp-block-cover__video-background', 'heading' => true, 'text' => true, 'background' => false, '__experimentalSkipSerialization' => array( 'gradients' ), 'enableContrastChecker' => false ), 'dimensions' => array( 'aspectRatio' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'layout' => array( 'allowJustification' => false ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-cover-editor', 'style' => 'wp-block-cover' ), 'details' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/details', 'title' => 'Details', 'category' => 'text', 'description' => 'Hide and show additional content.', 'keywords' => array( 'accordion', 'summary', 'toggle', 'disclosure' ), 'textdomain' => 'default', 'attributes' => array( 'showContent' => array( 'type' => 'boolean', 'default' => false ), 'summary' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'summary' ), 'name' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'name', 'selector' => '.wp-block-details' ), 'allowedBlocks' => array( 'type' => 'array' ), 'placeholder' => array( 'type' => 'string' ) ), 'supports' => array( '__experimentalOnEnter' => true, 'align' => array( 'wide', 'full' ), 'anchor' => true, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), '__experimentalBorder' => array( 'color' => true, 'width' => true, 'style' => true ), 'html' => false, 'spacing' => array( 'margin' => true, 'padding' => true, 'blockGap' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'layout' => array( 'allowEditing' => false ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-details-editor', 'style' => 'wp-block-details' ), 'embed' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/embed', 'title' => 'Embed', 'category' => 'embed', 'description' => 'Add a block that displays content pulled from other sites, like Twitter or YouTube.', 'textdomain' => 'default', 'attributes' => array( 'url' => array( 'type' => 'string', 'role' => 'content' ), 'caption' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'figcaption', 'role' => 'content' ), 'type' => array( 'type' => 'string', 'role' => 'content' ), 'providerNameSlug' => array( 'type' => 'string', 'role' => 'content' ), 'allowResponsive' => array( 'type' => 'boolean', 'default' => true ), 'responsive' => array( 'type' => 'boolean', 'default' => false, 'role' => 'content' ), 'previewable' => array( 'type' => 'boolean', 'default' => true, 'role' => 'content' ) ), 'supports' => array( 'align' => true, 'spacing' => array( 'margin' => true ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-embed-editor', 'style' => 'wp-block-embed' ), 'file' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/file', 'title' => 'File', 'category' => 'media', 'description' => 'Add a link to a downloadable file.', 'keywords' => array( 'document', 'pdf', 'download' ), 'textdomain' => 'default', 'attributes' => array( 'id' => array( 'type' => 'number' ), 'blob' => array( 'type' => 'string', 'role' => 'local' ), 'href' => array( 'type' => 'string', 'role' => 'content' ), 'fileId' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'a:not([download])', 'attribute' => 'id' ), 'fileName' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'a:not([download])', 'role' => 'content' ), 'textLinkHref' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'a:not([download])', 'attribute' => 'href', 'role' => 'content' ), 'textLinkTarget' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'a:not([download])', 'attribute' => 'target' ), 'showDownloadButton' => array( 'type' => 'boolean', 'default' => true ), 'downloadButtonText' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'a[download]', 'role' => 'content' ), 'displayPreview' => array( 'type' => 'boolean' ), 'previewHeight' => array( 'type' => 'number', 'default' => 600 ) ), 'supports' => array( 'anchor' => true, 'align' => true, 'spacing' => array( 'margin' => true, 'padding' => true ), 'color' => array( 'gradients' => true, 'link' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true, 'link' => true ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ), 'interactivity' => true ), 'editorStyle' => 'wp-block-file-editor', 'style' => 'wp-block-file' ), 'footnotes' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/footnotes', 'title' => 'Footnotes', 'category' => 'text', 'description' => 'Display footnotes added to the page.', 'keywords' => array( 'references' ), 'textdomain' => 'default', 'usesContext' => array( 'postId', 'postType' ), 'supports' => array( '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => false, 'color' => false, 'width' => false, 'style' => false ) ), 'color' => array( 'background' => true, 'link' => true, 'text' => true, '__experimentalDefaultControls' => array( 'link' => true, 'text' => true ) ), 'html' => false, 'multiple' => false, 'reusable' => false, 'inserter' => false, 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalTextDecoration' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalTextTransform' => true, '__experimentalWritingMode' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'style' => 'wp-block-footnotes' ), 'freeform' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/freeform', 'title' => 'Classic', 'category' => 'text', 'description' => 'Use the classic WordPress editor.', 'textdomain' => 'default', 'attributes' => array( 'content' => array( 'type' => 'string', 'source' => 'raw' ) ), 'supports' => array( 'className' => false, 'customClassName' => false, 'reusable' => false ), 'editorStyle' => 'wp-block-freeform-editor' ), 'gallery' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/gallery', 'title' => 'Gallery', 'category' => 'media', 'allowedBlocks' => array( 'core/image' ), 'description' => 'Display multiple images in a rich gallery.', 'keywords' => array( 'images', 'photos' ), 'textdomain' => 'default', 'attributes' => array( 'images' => array( 'type' => 'array', 'default' => array( ), 'source' => 'query', 'selector' => '.blocks-gallery-item', 'query' => array( 'url' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'img', 'attribute' => 'src' ), 'fullUrl' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'img', 'attribute' => 'data-full-url' ), 'link' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'img', 'attribute' => 'data-link' ), 'alt' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'img', 'attribute' => 'alt', 'default' => '' ), 'id' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'img', 'attribute' => 'data-id' ), 'caption' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => '.blocks-gallery-item__caption' ) ) ), 'ids' => array( 'type' => 'array', 'items' => array( 'type' => 'number' ), 'default' => array( ) ), 'shortCodeTransforms' => array( 'type' => 'array', 'items' => array( 'type' => 'object' ), 'default' => array( ) ), 'columns' => array( 'type' => 'number', 'minimum' => 1, 'maximum' => 8 ), 'caption' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => '.blocks-gallery-caption' ), 'imageCrop' => array( 'type' => 'boolean', 'default' => true ), 'randomOrder' => array( 'type' => 'boolean', 'default' => false ), 'fixedHeight' => array( 'type' => 'boolean', 'default' => true ), 'linkTarget' => array( 'type' => 'string' ), 'linkTo' => array( 'type' => 'string' ), 'sizeSlug' => array( 'type' => 'string', 'default' => 'large' ), 'allowResize' => array( 'type' => 'boolean', 'default' => false ) ), 'providesContext' => array( 'allowResize' => 'allowResize', 'imageCrop' => 'imageCrop', 'fixedHeight' => 'fixedHeight' ), 'supports' => array( 'anchor' => true, 'align' => true, '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true ) ), 'html' => false, 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ), 'spacing' => array( 'margin' => true, 'padding' => true, 'blockGap' => array( 'horizontal', 'vertical' ), '__experimentalSkipSerialization' => array( 'blockGap' ), '__experimentalDefaultControls' => array( 'blockGap' => true, 'margin' => false, 'padding' => false ) ), 'color' => array( 'text' => false, 'background' => true, 'gradients' => true ), 'layout' => array( 'allowSwitching' => false, 'allowInheriting' => false, 'allowEditing' => false, 'default' => array( 'type' => 'flex' ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-gallery-editor', 'style' => 'wp-block-gallery' ), 'group' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/group', 'title' => 'Group', 'category' => 'design', 'description' => 'Gather blocks in a layout container.', 'keywords' => array( 'container', 'wrapper', 'row', 'section' ), 'textdomain' => 'default', 'attributes' => array( 'tagName' => array( 'type' => 'string', 'default' => 'div' ), 'templateLock' => array( 'type' => array( 'string', 'boolean' ), 'enum' => array( 'all', 'insert', 'contentOnly', false ) ), 'allowedBlocks' => array( 'type' => 'array' ) ), 'supports' => array( '__experimentalOnEnter' => true, '__experimentalOnMerge' => true, '__experimentalSettings' => true, 'align' => array( 'wide', 'full' ), 'anchor' => true, 'ariaLabel' => true, 'html' => false, 'background' => array( 'backgroundImage' => true, 'backgroundSize' => true, '__experimentalDefaultControls' => array( 'backgroundImage' => true ) ), 'color' => array( 'gradients' => true, 'heading' => true, 'button' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'shadow' => true, 'spacing' => array( 'margin' => array( 'top', 'bottom' ), 'padding' => true, 'blockGap' => true, '__experimentalDefaultControls' => array( 'padding' => true, 'blockGap' => true ) ), 'dimensions' => array( 'minHeight' => true ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ) ), 'position' => array( 'sticky' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'layout' => array( 'allowSizingOnChildren' => true ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-group-editor', 'style' => 'wp-block-group' ), 'heading' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/heading', 'title' => 'Heading', 'category' => 'text', 'description' => 'Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content.', 'keywords' => array( 'title', 'subtitle' ), 'textdomain' => 'default', 'attributes' => array( 'textAlign' => array( 'type' => 'string' ), 'content' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'h1,h2,h3,h4,h5,h6', 'role' => 'content' ), 'level' => array( 'type' => 'number', 'default' => 2 ), 'levelOptions' => array( 'type' => 'array' ), 'placeholder' => array( 'type' => 'string' ) ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'anchor' => true, 'className' => true, 'splitting' => true, '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ), 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalWritingMode' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__unstablePasteTextInline' => true, '__experimentalSlashInserter' => true, 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-heading-editor', 'style' => 'wp-block-heading' ), 'home-link' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/home-link', 'category' => 'design', 'parent' => array( 'core/navigation' ), 'title' => 'Home Link', 'description' => 'Create a link that always points to the homepage of the site. Usually not necessary if there is already a site title link present in the header.', 'textdomain' => 'default', 'attributes' => array( 'label' => array( 'type' => 'string' ) ), 'usesContext' => array( 'textColor', 'customTextColor', 'backgroundColor', 'customBackgroundColor', 'fontSize', 'customFontSize', 'style' ), 'supports' => array( 'reusable' => false, 'html' => false, 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-home-link-editor', 'style' => 'wp-block-home-link' ), 'html' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/html', 'title' => 'Custom HTML', 'category' => 'widgets', 'description' => 'Add custom HTML code and preview it as you edit.', 'keywords' => array( 'embed' ), 'textdomain' => 'default', 'attributes' => array( 'content' => array( 'type' => 'string', 'source' => 'raw' ) ), 'supports' => array( 'customClassName' => false, 'className' => false, 'html' => false, 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-html-editor' ), 'image' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/image', 'title' => 'Image', 'category' => 'media', 'usesContext' => array( 'allowResize', 'imageCrop', 'fixedHeight', 'postId', 'postType', 'queryId' ), 'description' => 'Insert an image to make a visual statement.', 'keywords' => array( 'img', 'photo', 'picture' ), 'textdomain' => 'default', 'attributes' => array( 'blob' => array( 'type' => 'string', 'role' => 'local' ), 'url' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'img', 'attribute' => 'src', 'role' => 'content' ), 'alt' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'img', 'attribute' => 'alt', 'default' => '', 'role' => 'content' ), 'caption' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'figcaption', 'role' => 'content' ), 'lightbox' => array( 'type' => 'object', 'enabled' => array( 'type' => 'boolean' ) ), 'title' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'img', 'attribute' => 'title', 'role' => 'content' ), 'href' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'figure > a', 'attribute' => 'href', 'role' => 'content' ), 'rel' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'figure > a', 'attribute' => 'rel' ), 'linkClass' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'figure > a', 'attribute' => 'class' ), 'id' => array( 'type' => 'number', 'role' => 'content' ), 'width' => array( 'type' => 'string' ), 'height' => array( 'type' => 'string' ), 'aspectRatio' => array( 'type' => 'string' ), 'scale' => array( 'type' => 'string' ), 'sizeSlug' => array( 'type' => 'string' ), 'linkDestination' => array( 'type' => 'string' ), 'linkTarget' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'figure > a', 'attribute' => 'target' ) ), 'supports' => array( 'interactivity' => true, 'align' => array( 'left', 'center', 'right', 'wide', 'full' ), 'anchor' => true, 'color' => array( 'text' => false, 'background' => false ), 'filter' => array( 'duotone' => true ), 'spacing' => array( 'margin' => true ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'width' => true, '__experimentalSkipSerialization' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'width' => true ) ), 'shadow' => array( '__experimentalSkipSerialization' => true ) ), 'selectors' => array( 'border' => '.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder', 'shadow' => '.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder', 'filter' => array( 'duotone' => '.wp-block-image img, .wp-block-image .components-placeholder' ) ), 'styles' => array( array( 'name' => 'default', 'label' => 'Default', 'isDefault' => true ), array( 'name' => 'rounded', 'label' => 'Rounded' ) ), 'editorStyle' => 'wp-block-image-editor', 'style' => 'wp-block-image' ), 'latest-comments' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/latest-comments', 'title' => 'Latest Comments', 'category' => 'widgets', 'description' => 'Display a list of your most recent comments.', 'keywords' => array( 'recent comments' ), 'textdomain' => 'default', 'attributes' => array( 'commentsToShow' => array( 'type' => 'number', 'default' => 5, 'minimum' => 1, 'maximum' => 100 ), 'displayAvatar' => array( 'type' => 'boolean', 'default' => true ), 'displayDate' => array( 'type' => 'boolean', 'default' => true ), 'displayExcerpt' => array( 'type' => 'boolean', 'default' => true ) ), 'supports' => array( 'align' => true, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'html' => false, 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-latest-comments-editor', 'style' => 'wp-block-latest-comments' ), 'latest-posts' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/latest-posts', 'title' => 'Latest Posts', 'category' => 'widgets', 'description' => 'Display a list of your most recent posts.', 'keywords' => array( 'recent posts' ), 'textdomain' => 'default', 'attributes' => array( 'categories' => array( 'type' => 'array', 'items' => array( 'type' => 'object' ) ), 'selectedAuthor' => array( 'type' => 'number' ), 'postsToShow' => array( 'type' => 'number', 'default' => 5 ), 'displayPostContent' => array( 'type' => 'boolean', 'default' => false ), 'displayPostContentRadio' => array( 'type' => 'string', 'default' => 'excerpt' ), 'excerptLength' => array( 'type' => 'number', 'default' => 55 ), 'displayAuthor' => array( 'type' => 'boolean', 'default' => false ), 'displayPostDate' => array( 'type' => 'boolean', 'default' => false ), 'postLayout' => array( 'type' => 'string', 'default' => 'list' ), 'columns' => array( 'type' => 'number', 'default' => 3 ), 'order' => array( 'type' => 'string', 'default' => 'desc' ), 'orderBy' => array( 'type' => 'string', 'default' => 'date' ), 'displayFeaturedImage' => array( 'type' => 'boolean', 'default' => false ), 'featuredImageAlign' => array( 'type' => 'string', 'enum' => array( 'left', 'center', 'right' ) ), 'featuredImageSizeSlug' => array( 'type' => 'string', 'default' => 'thumbnail' ), 'featuredImageSizeWidth' => array( 'type' => 'number', 'default' => null ), 'featuredImageSizeHeight' => array( 'type' => 'number', 'default' => null ), 'addLinkToFeaturedImage' => array( 'type' => 'boolean', 'default' => false ) ), 'supports' => array( 'align' => true, 'html' => false, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-latest-posts-editor', 'style' => 'wp-block-latest-posts' ), 'legacy-widget' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/legacy-widget', 'title' => 'Legacy Widget', 'category' => 'widgets', 'description' => 'Display a legacy widget.', 'textdomain' => 'default', 'attributes' => array( 'id' => array( 'type' => 'string', 'default' => null ), 'idBase' => array( 'type' => 'string', 'default' => null ), 'instance' => array( 'type' => 'object', 'default' => null ) ), 'supports' => array( 'html' => false, 'customClassName' => false, 'reusable' => false ), 'editorStyle' => 'wp-block-legacy-widget-editor' ), 'list' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/list', 'title' => 'List', 'category' => 'text', 'allowedBlocks' => array( 'core/list-item' ), 'description' => 'An organized collection of items displayed in a specific order.', 'keywords' => array( 'bullet list', 'ordered list', 'numbered list' ), 'textdomain' => 'default', 'attributes' => array( 'ordered' => array( 'type' => 'boolean', 'default' => false, 'role' => 'content' ), 'values' => array( 'type' => 'string', 'source' => 'html', 'selector' => 'ol,ul', 'multiline' => 'li', '__unstableMultilineWrapperTags' => array( 'ol', 'ul' ), 'default' => '', 'role' => 'content' ), 'type' => array( 'type' => 'string' ), 'start' => array( 'type' => 'number' ), 'reversed' => array( 'type' => 'boolean' ), 'placeholder' => array( 'type' => 'string' ) ), 'supports' => array( 'anchor' => true, 'html' => false, '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), '__unstablePasteTextInline' => true, '__experimentalOnMerge' => true, '__experimentalSlashInserter' => true, 'interactivity' => array( 'clientNavigation' => true ) ), 'selectors' => array( 'border' => '.wp-block-list:not(.wp-block-list .wp-block-list)' ), 'editorStyle' => 'wp-block-list-editor', 'style' => 'wp-block-list' ), 'list-item' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/list-item', 'title' => 'List Item', 'category' => 'text', 'parent' => array( 'core/list' ), 'allowedBlocks' => array( 'core/list' ), 'description' => 'An individual item within a list.', 'textdomain' => 'default', 'attributes' => array( 'placeholder' => array( 'type' => 'string' ), 'content' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'li', 'role' => 'content' ) ), 'supports' => array( 'anchor' => true, 'className' => false, 'splitting' => true, '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ), 'color' => array( 'gradients' => true, 'link' => true, 'background' => true, '__experimentalDefaultControls' => array( 'text' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'selectors' => array( 'root' => '.wp-block-list > li', 'border' => '.wp-block-list:not(.wp-block-list .wp-block-list) > li' ) ), 'loginout' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/loginout', 'title' => 'Login/out', 'category' => 'theme', 'description' => 'Show login & logout links.', 'keywords' => array( 'login', 'logout', 'form' ), 'textdomain' => 'default', 'attributes' => array( 'displayLoginAsForm' => array( 'type' => 'boolean', 'default' => false ), 'redirectToCurrent' => array( 'type' => 'boolean', 'default' => true ) ), 'example' => array( 'viewportWidth' => 350 ), 'supports' => array( 'className' => true, 'color' => array( 'background' => true, 'text' => false, 'gradients' => true, 'link' => true ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ), 'interactivity' => array( 'clientNavigation' => true ) ), 'style' => 'wp-block-loginout' ), 'media-text' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/media-text', 'title' => 'Media & Text', 'category' => 'media', 'description' => 'Set media and words side-by-side for a richer layout.', 'keywords' => array( 'image', 'video' ), 'textdomain' => 'default', 'attributes' => array( 'align' => array( 'type' => 'string', 'default' => 'none' ), 'mediaAlt' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'figure img', 'attribute' => 'alt', 'default' => '', 'role' => 'content' ), 'mediaPosition' => array( 'type' => 'string', 'default' => 'left' ), 'mediaId' => array( 'type' => 'number', 'role' => 'content' ), 'mediaUrl' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'figure video,figure img', 'attribute' => 'src', 'role' => 'content' ), 'mediaLink' => array( 'type' => 'string' ), 'linkDestination' => array( 'type' => 'string' ), 'linkTarget' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'figure a', 'attribute' => 'target' ), 'href' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'figure a', 'attribute' => 'href', 'role' => 'content' ), 'rel' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'figure a', 'attribute' => 'rel' ), 'linkClass' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'figure a', 'attribute' => 'class' ), 'mediaType' => array( 'type' => 'string', 'role' => 'content' ), 'mediaWidth' => array( 'type' => 'number', 'default' => 50 ), 'mediaSizeSlug' => array( 'type' => 'string' ), 'isStackedOnMobile' => array( 'type' => 'boolean', 'default' => true ), 'verticalAlignment' => array( 'type' => 'string' ), 'imageFill' => array( 'type' => 'boolean' ), 'focalPoint' => array( 'type' => 'object' ), 'allowedBlocks' => array( 'type' => 'array' ), 'useFeaturedImage' => array( 'type' => 'boolean', 'default' => false ) ), 'usesContext' => array( 'postId', 'postType' ), 'supports' => array( 'anchor' => true, 'align' => array( 'wide', 'full' ), 'html' => false, '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ) ), 'color' => array( 'gradients' => true, 'heading' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-media-text-editor', 'style' => 'wp-block-media-text' ), 'missing' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/missing', 'title' => 'Unsupported', 'category' => 'text', 'description' => 'Your site doesn’t include support for this block.', 'textdomain' => 'default', 'attributes' => array( 'originalName' => array( 'type' => 'string' ), 'originalUndelimitedContent' => array( 'type' => 'string' ), 'originalContent' => array( 'type' => 'string', 'source' => 'raw' ) ), 'supports' => array( 'className' => false, 'customClassName' => false, 'inserter' => false, 'html' => false, 'reusable' => false, 'interactivity' => array( 'clientNavigation' => true ) ) ), 'more' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/more', 'title' => 'More', 'category' => 'design', 'description' => 'Content before this block will be shown in the excerpt on your archives page.', 'keywords' => array( 'read more' ), 'textdomain' => 'default', 'attributes' => array( 'customText' => array( 'type' => 'string', 'default' => '' ), 'noTeaser' => array( 'type' => 'boolean', 'default' => false ) ), 'supports' => array( 'customClassName' => false, 'className' => false, 'html' => false, 'multiple' => false, 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-more-editor' ), 'navigation' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/navigation', 'title' => 'Navigation', 'category' => 'theme', 'allowedBlocks' => array( 'core/navigation-link', 'core/search', 'core/social-links', 'core/page-list', 'core/spacer', 'core/home-link', 'core/site-title', 'core/site-logo', 'core/navigation-submenu', 'core/loginout', 'core/buttons' ), 'description' => 'A collection of blocks that allow visitors to get around your site.', 'keywords' => array( 'menu', 'navigation', 'links' ), 'textdomain' => 'default', 'attributes' => array( 'ref' => array( 'type' => 'number' ), 'textColor' => array( 'type' => 'string' ), 'customTextColor' => array( 'type' => 'string' ), 'rgbTextColor' => array( 'type' => 'string' ), 'backgroundColor' => array( 'type' => 'string' ), 'customBackgroundColor' => array( 'type' => 'string' ), 'rgbBackgroundColor' => array( 'type' => 'string' ), 'showSubmenuIcon' => array( 'type' => 'boolean', 'default' => true ), 'openSubmenusOnClick' => array( 'type' => 'boolean', 'default' => false ), 'overlayMenu' => array( 'type' => 'string', 'default' => 'mobile' ), 'icon' => array( 'type' => 'string', 'default' => 'handle' ), 'hasIcon' => array( 'type' => 'boolean', 'default' => true ), '__unstableLocation' => array( 'type' => 'string' ), 'overlayBackgroundColor' => array( 'type' => 'string' ), 'customOverlayBackgroundColor' => array( 'type' => 'string' ), 'overlayTextColor' => array( 'type' => 'string' ), 'customOverlayTextColor' => array( 'type' => 'string' ), 'maxNestingLevel' => array( 'type' => 'number', 'default' => 5 ), 'templateLock' => array( 'type' => array( 'string', 'boolean' ), 'enum' => array( 'all', 'insert', 'contentOnly', false ) ) ), 'providesContext' => array( 'textColor' => 'textColor', 'customTextColor' => 'customTextColor', 'backgroundColor' => 'backgroundColor', 'customBackgroundColor' => 'customBackgroundColor', 'overlayTextColor' => 'overlayTextColor', 'customOverlayTextColor' => 'customOverlayTextColor', 'overlayBackgroundColor' => 'overlayBackgroundColor', 'customOverlayBackgroundColor' => 'customOverlayBackgroundColor', 'fontSize' => 'fontSize', 'customFontSize' => 'customFontSize', 'showSubmenuIcon' => 'showSubmenuIcon', 'openSubmenusOnClick' => 'openSubmenusOnClick', 'style' => 'style', 'maxNestingLevel' => 'maxNestingLevel' ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'ariaLabel' => true, 'html' => false, 'inserter' => true, 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalTextTransform' => true, '__experimentalFontFamily' => true, '__experimentalLetterSpacing' => true, '__experimentalTextDecoration' => true, '__experimentalSkipSerialization' => array( 'textDecoration' ), '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'spacing' => array( 'blockGap' => true, 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ), '__experimentalDefaultControls' => array( 'blockGap' => true ) ), 'layout' => array( 'allowSwitching' => false, 'allowInheriting' => false, 'allowVerticalAlignment' => false, 'allowSizingOnChildren' => true, 'default' => array( 'type' => 'flex' ) ), 'interactivity' => true, 'renaming' => false ), 'editorStyle' => 'wp-block-navigation-editor', 'style' => 'wp-block-navigation' ), 'navigation-link' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/navigation-link', 'title' => 'Custom Link', 'category' => 'design', 'parent' => array( 'core/navigation' ), 'allowedBlocks' => array( 'core/navigation-link', 'core/navigation-submenu', 'core/page-list' ), 'description' => 'Add a page, link, or another item to your navigation.', 'textdomain' => 'default', 'attributes' => array( 'label' => array( 'type' => 'string' ), 'type' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ), 'rel' => array( 'type' => 'string' ), 'id' => array( 'type' => 'number' ), 'opensInNewTab' => array( 'type' => 'boolean', 'default' => false ), 'url' => array( 'type' => 'string' ), 'title' => array( 'type' => 'string' ), 'kind' => array( 'type' => 'string' ), 'isTopLevelLink' => array( 'type' => 'boolean' ) ), 'usesContext' => array( 'textColor', 'customTextColor', 'backgroundColor', 'customBackgroundColor', 'overlayTextColor', 'customOverlayTextColor', 'overlayBackgroundColor', 'customOverlayBackgroundColor', 'fontSize', 'customFontSize', 'showSubmenuIcon', 'maxNestingLevel', 'style' ), 'supports' => array( 'reusable' => false, 'html' => false, '__experimentalSlashInserter' => true, 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'renaming' => false, 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-navigation-link-editor', 'style' => 'wp-block-navigation-link' ), 'navigation-submenu' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/navigation-submenu', 'title' => 'Submenu', 'category' => 'design', 'parent' => array( 'core/navigation' ), 'description' => 'Add a submenu to your navigation.', 'textdomain' => 'default', 'attributes' => array( 'label' => array( 'type' => 'string' ), 'type' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ), 'rel' => array( 'type' => 'string' ), 'id' => array( 'type' => 'number' ), 'opensInNewTab' => array( 'type' => 'boolean', 'default' => false ), 'url' => array( 'type' => 'string' ), 'title' => array( 'type' => 'string' ), 'kind' => array( 'type' => 'string' ), 'isTopLevelItem' => array( 'type' => 'boolean' ) ), 'usesContext' => array( 'textColor', 'customTextColor', 'backgroundColor', 'customBackgroundColor', 'overlayTextColor', 'customOverlayTextColor', 'overlayBackgroundColor', 'customOverlayBackgroundColor', 'fontSize', 'customFontSize', 'showSubmenuIcon', 'maxNestingLevel', 'openSubmenusOnClick', 'style' ), 'supports' => array( 'reusable' => false, 'html' => false, 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-navigation-submenu-editor', 'style' => 'wp-block-navigation-submenu' ), 'nextpage' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/nextpage', 'title' => 'Page Break', 'category' => 'design', 'description' => 'Separate your content into a multi-page experience.', 'keywords' => array( 'next page', 'pagination' ), 'parent' => array( 'core/post-content' ), 'textdomain' => 'default', 'supports' => array( 'customClassName' => false, 'className' => false, 'html' => false, 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-nextpage-editor' ), 'page-list' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/page-list', 'title' => 'Page List', 'category' => 'widgets', 'allowedBlocks' => array( 'core/page-list-item' ), 'description' => 'Display a list of all pages.', 'keywords' => array( 'menu', 'navigation' ), 'textdomain' => 'default', 'attributes' => array( 'parentPageID' => array( 'type' => 'integer', 'default' => 0 ), 'isNested' => array( 'type' => 'boolean', 'default' => false ) ), 'usesContext' => array( 'textColor', 'customTextColor', 'backgroundColor', 'customBackgroundColor', 'overlayTextColor', 'customOverlayTextColor', 'overlayBackgroundColor', 'customOverlayBackgroundColor', 'fontSize', 'customFontSize', 'showSubmenuIcon', 'style', 'openSubmenusOnClick' ), 'supports' => array( 'reusable' => false, 'html' => false, 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), 'color' => array( 'text' => true, 'background' => true, 'link' => true, 'gradients' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ), 'spacing' => array( 'padding' => true, 'margin' => true, '__experimentalDefaultControls' => array( 'padding' => false, 'margin' => false ) ) ), 'editorStyle' => 'wp-block-page-list-editor', 'style' => 'wp-block-page-list' ), 'page-list-item' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/page-list-item', 'title' => 'Page List Item', 'category' => 'widgets', 'parent' => array( 'core/page-list' ), 'description' => 'Displays a page inside a list of all pages.', 'keywords' => array( 'page', 'menu', 'navigation' ), 'textdomain' => 'default', 'attributes' => array( 'id' => array( 'type' => 'number' ), 'label' => array( 'type' => 'string' ), 'title' => array( 'type' => 'string' ), 'link' => array( 'type' => 'string' ), 'hasChildren' => array( 'type' => 'boolean' ) ), 'usesContext' => array( 'textColor', 'customTextColor', 'backgroundColor', 'customBackgroundColor', 'overlayTextColor', 'customOverlayTextColor', 'overlayBackgroundColor', 'customOverlayBackgroundColor', 'fontSize', 'customFontSize', 'showSubmenuIcon', 'style', 'openSubmenusOnClick' ), 'supports' => array( 'reusable' => false, 'html' => false, 'lock' => false, 'inserter' => false, '__experimentalToolbar' => false, 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-page-list-editor', 'style' => 'wp-block-page-list' ), 'paragraph' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/paragraph', 'title' => 'Paragraph', 'category' => 'text', 'description' => 'Start with the basic building block of all narrative.', 'keywords' => array( 'text' ), 'textdomain' => 'default', 'attributes' => array( 'align' => array( 'type' => 'string' ), 'content' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'p', 'role' => 'content' ), 'dropCap' => array( 'type' => 'boolean', 'default' => false ), 'placeholder' => array( 'type' => 'string' ), 'direction' => array( 'type' => 'string', 'enum' => array( 'ltr', 'rtl' ) ) ), 'supports' => array( 'splitting' => true, 'anchor' => true, 'className' => false, '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ), 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalTextDecoration' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalTextTransform' => true, '__experimentalWritingMode' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalSelector' => 'p', '__unstablePasteTextInline' => true, 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-paragraph-editor', 'style' => 'wp-block-paragraph' ), 'pattern' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/pattern', 'title' => 'Pattern Placeholder', 'category' => 'theme', 'description' => 'Show a block pattern.', 'supports' => array( 'html' => false, 'inserter' => false, 'renaming' => false, 'interactivity' => array( 'clientNavigation' => true ) ), 'textdomain' => 'default', 'attributes' => array( 'slug' => array( 'type' => 'string' ) ) ), 'post-author' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-author', 'title' => 'Author', 'category' => 'theme', 'description' => 'Display post author details such as name, avatar, and bio.', 'textdomain' => 'default', 'attributes' => array( 'textAlign' => array( 'type' => 'string' ), 'avatarSize' => array( 'type' => 'number', 'default' => 48 ), 'showAvatar' => array( 'type' => 'boolean', 'default' => true ), 'showBio' => array( 'type' => 'boolean' ), 'byline' => array( 'type' => 'string' ), 'isLink' => array( 'type' => 'boolean', 'default' => false, 'role' => 'content' ), 'linkTarget' => array( 'type' => 'string', 'default' => '_self', 'role' => 'content' ) ), 'usesContext' => array( 'postType', 'postId', 'queryId' ), 'supports' => array( 'html' => false, 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDuotone' => '.wp-block-post-author__avatar img', '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'editorStyle' => 'wp-block-post-author-editor', 'style' => 'wp-block-post-author' ), 'post-author-biography' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-author-biography', 'title' => 'Author Biography', 'category' => 'theme', 'description' => 'The author biography.', 'textdomain' => 'default', 'attributes' => array( 'textAlign' => array( 'type' => 'string' ) ), 'usesContext' => array( 'postType', 'postId' ), 'example' => array( 'viewportWidth' => 350 ), 'supports' => array( 'spacing' => array( 'margin' => true, 'padding' => true ), 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'style' => 'wp-block-post-author-biography' ), 'post-author-name' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-author-name', 'title' => 'Author Name', 'category' => 'theme', 'description' => 'The author name.', 'textdomain' => 'default', 'attributes' => array( 'textAlign' => array( 'type' => 'string' ), 'isLink' => array( 'type' => 'boolean', 'default' => false, 'role' => 'content' ), 'linkTarget' => array( 'type' => 'string', 'default' => '_self', 'role' => 'content' ) ), 'usesContext' => array( 'postType', 'postId' ), 'example' => array( 'viewportWidth' => 350 ), 'supports' => array( 'html' => false, 'spacing' => array( 'margin' => true, 'padding' => true ), 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'style' => 'wp-block-post-author-name' ), 'post-comments-form' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-comments-form', 'title' => 'Comments Form', 'category' => 'theme', 'description' => 'Display a post\'s comments form.', 'textdomain' => 'default', 'attributes' => array( 'textAlign' => array( 'type' => 'string' ) ), 'usesContext' => array( 'postId', 'postType' ), 'supports' => array( 'html' => false, 'color' => array( 'gradients' => true, 'heading' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalTextTransform' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'editorStyle' => 'wp-block-post-comments-form-editor', 'style' => array( 'wp-block-post-comments-form', 'wp-block-buttons', 'wp-block-button' ), 'example' => array( 'attributes' => array( 'textAlign' => 'center' ) ) ), 'post-content' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-content', 'title' => 'Content', 'category' => 'theme', 'description' => 'Displays the contents of a post or page.', 'textdomain' => 'default', 'usesContext' => array( 'postId', 'postType', 'queryId' ), 'example' => array( 'viewportWidth' => 350 ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'html' => false, 'layout' => true, 'background' => array( 'backgroundImage' => true, 'backgroundSize' => true, '__experimentalDefaultControls' => array( 'backgroundImage' => true ) ), 'dimensions' => array( 'minHeight' => true ), 'spacing' => array( 'blockGap' => true, 'padding' => true, 'margin' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'color' => array( 'gradients' => true, 'heading' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => false, 'text' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'style' => 'wp-block-post-content', 'editorStyle' => 'wp-block-post-content-editor' ), 'post-date' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-date', 'title' => 'Date', 'category' => 'theme', 'description' => 'Display the publish date for an entry such as a post or page.', 'textdomain' => 'default', 'attributes' => array( 'textAlign' => array( 'type' => 'string' ), 'format' => array( 'type' => 'string' ), 'isLink' => array( 'type' => 'boolean', 'default' => false, 'role' => 'content' ), 'displayType' => array( 'type' => 'string', 'default' => 'date' ) ), 'usesContext' => array( 'postId', 'postType', 'queryId' ), 'example' => array( 'viewportWidth' => 350 ), 'supports' => array( 'html' => false, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ) ), 'post-excerpt' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-excerpt', 'title' => 'Excerpt', 'category' => 'theme', 'description' => 'Display the excerpt.', 'textdomain' => 'default', 'attributes' => array( 'textAlign' => array( 'type' => 'string' ), 'moreText' => array( 'type' => 'string' ), 'showMoreOnNewLine' => array( 'type' => 'boolean', 'default' => true ), 'excerptLength' => array( 'type' => 'number', 'default' => 55 ) ), 'usesContext' => array( 'postId', 'postType', 'queryId' ), 'example' => array( 'viewportWidth' => 350 ), 'supports' => array( 'html' => false, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'editorStyle' => 'wp-block-post-excerpt-editor', 'style' => 'wp-block-post-excerpt' ), 'post-featured-image' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-featured-image', 'title' => 'Featured Image', 'category' => 'theme', 'description' => 'Display a post\'s featured image.', 'textdomain' => 'default', 'attributes' => array( 'isLink' => array( 'type' => 'boolean', 'default' => false, 'role' => 'content' ), 'aspectRatio' => array( 'type' => 'string' ), 'width' => array( 'type' => 'string' ), 'height' => array( 'type' => 'string' ), 'scale' => array( 'type' => 'string', 'default' => 'cover' ), 'sizeSlug' => array( 'type' => 'string' ), 'rel' => array( 'type' => 'string', 'attribute' => 'rel', 'default' => '', 'role' => 'content' ), 'linkTarget' => array( 'type' => 'string', 'default' => '_self', 'role' => 'content' ), 'overlayColor' => array( 'type' => 'string' ), 'customOverlayColor' => array( 'type' => 'string' ), 'dimRatio' => array( 'type' => 'number', 'default' => 0 ), 'gradient' => array( 'type' => 'string' ), 'customGradient' => array( 'type' => 'string' ), 'useFirstImageFromPost' => array( 'type' => 'boolean', 'default' => false ) ), 'usesContext' => array( 'postId', 'postType', 'queryId' ), 'example' => array( 'viewportWidth' => 350 ), 'supports' => array( 'align' => array( 'left', 'right', 'center', 'wide', 'full' ), 'color' => array( 'text' => false, 'background' => false ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'width' => true, '__experimentalSkipSerialization' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'width' => true ) ), 'filter' => array( 'duotone' => true ), 'shadow' => array( '__experimentalSkipSerialization' => true ), 'html' => false, 'spacing' => array( 'margin' => true, 'padding' => true ), 'interactivity' => array( 'clientNavigation' => true ) ), 'selectors' => array( 'border' => '.wp-block-post-featured-image img, .wp-block-post-featured-image .block-editor-media-placeholder, .wp-block-post-featured-image .wp-block-post-featured-image__overlay', 'shadow' => '.wp-block-post-featured-image img, .wp-block-post-featured-image .components-placeholder', 'filter' => array( 'duotone' => '.wp-block-post-featured-image img, .wp-block-post-featured-image .wp-block-post-featured-image__placeholder, .wp-block-post-featured-image .components-placeholder__illustration, .wp-block-post-featured-image .components-placeholder::before' ) ), 'editorStyle' => 'wp-block-post-featured-image-editor', 'style' => 'wp-block-post-featured-image' ), 'post-navigation-link' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-navigation-link', 'title' => 'Post Navigation Link', 'category' => 'theme', 'description' => 'Displays the next or previous post link that is adjacent to the current post.', 'textdomain' => 'default', 'attributes' => array( 'textAlign' => array( 'type' => 'string' ), 'type' => array( 'type' => 'string', 'default' => 'next' ), 'label' => array( 'type' => 'string' ), 'showTitle' => array( 'type' => 'boolean', 'default' => false ), 'linkLabel' => array( 'type' => 'boolean', 'default' => false ), 'arrow' => array( 'type' => 'string', 'default' => 'none' ), 'taxonomy' => array( 'type' => 'string', 'default' => '' ) ), 'usesContext' => array( 'postType' ), 'supports' => array( 'reusable' => false, 'html' => false, 'color' => array( 'link' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalWritingMode' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'style' => 'wp-block-post-navigation-link' ), 'post-template' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-template', 'title' => 'Post Template', 'category' => 'theme', 'ancestor' => array( 'core/query' ), 'description' => 'Contains the block elements used to render a post, like the title, date, featured image, content or excerpt, and more.', 'textdomain' => 'default', 'usesContext' => array( 'queryId', 'query', 'displayLayout', 'templateSlug', 'previewPostType', 'enhancedPagination', 'postType' ), 'supports' => array( 'reusable' => false, 'html' => false, 'align' => array( 'wide', 'full' ), 'layout' => true, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true, 'blockGap' => array( '__experimentalDefault' => '1.25em' ), '__experimentalDefaultControls' => array( 'blockGap' => true, 'padding' => false, 'margin' => false ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ), 'style' => 'wp-block-post-template', 'editorStyle' => 'wp-block-post-template-editor' ), 'post-terms' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-terms', 'title' => 'Post Terms', 'category' => 'theme', 'description' => 'Post terms.', 'textdomain' => 'default', 'attributes' => array( 'term' => array( 'type' => 'string' ), 'textAlign' => array( 'type' => 'string' ), 'separator' => array( 'type' => 'string', 'default' => ', ' ), 'prefix' => array( 'type' => 'string', 'default' => '' ), 'suffix' => array( 'type' => 'string', 'default' => '' ) ), 'usesContext' => array( 'postId', 'postType' ), 'example' => array( 'viewportWidth' => 350 ), 'supports' => array( 'html' => false, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'style' => 'wp-block-post-terms' ), 'post-title' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/post-title', 'title' => 'Title', 'category' => 'theme', 'description' => 'Displays the title of a post, page, or any other content-type.', 'textdomain' => 'default', 'usesContext' => array( 'postId', 'postType', 'queryId' ), 'attributes' => array( 'textAlign' => array( 'type' => 'string' ), 'level' => array( 'type' => 'number', 'default' => 2 ), 'levelOptions' => array( 'type' => 'array' ), 'isLink' => array( 'type' => 'boolean', 'default' => false, 'role' => 'content' ), 'rel' => array( 'type' => 'string', 'attribute' => 'rel', 'default' => '', 'role' => 'content' ), 'linkTarget' => array( 'type' => 'string', 'default' => '_self', 'role' => 'content' ) ), 'example' => array( 'viewportWidth' => 350 ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'html' => false, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'style' => 'wp-block-post-title' ), 'preformatted' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/preformatted', 'title' => 'Preformatted', 'category' => 'text', 'description' => 'Add text that respects your spacing and tabs, and also allows styling.', 'textdomain' => 'default', 'attributes' => array( 'content' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'pre', '__unstablePreserveWhiteSpace' => true, 'role' => 'content' ) ), 'supports' => array( 'anchor' => true, 'color' => array( 'gradients' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'padding' => true, 'margin' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'style' => 'wp-block-preformatted' ), 'pullquote' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/pullquote', 'title' => 'Pullquote', 'category' => 'text', 'description' => 'Give special visual emphasis to a quote from your text.', 'textdomain' => 'default', 'attributes' => array( 'value' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'p', 'role' => 'content' ), 'citation' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'cite', 'role' => 'content' ), 'textAlign' => array( 'type' => 'string' ) ), 'supports' => array( 'anchor' => true, 'align' => array( 'left', 'right', 'wide', 'full' ), 'background' => array( 'backgroundImage' => true, 'backgroundSize' => true, '__experimentalDefaultControls' => array( 'backgroundImage' => true ) ), 'color' => array( 'gradients' => true, 'background' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'dimensions' => array( 'minHeight' => true, '__experimentalDefaultControls' => array( 'minHeight' => false ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ) ), '__experimentalStyle' => array( 'typography' => array( 'fontSize' => '1.5em', 'lineHeight' => '1.6' ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-pullquote-editor', 'style' => 'wp-block-pullquote' ), 'query' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/query', 'title' => 'Query Loop', 'category' => 'theme', 'description' => 'An advanced block that allows displaying post types based on different query parameters and visual configurations.', 'keywords' => array( 'posts', 'list', 'blog', 'blogs', 'custom post types' ), 'textdomain' => 'default', 'attributes' => array( 'queryId' => array( 'type' => 'number' ), 'query' => array( 'type' => 'object', 'default' => array( 'perPage' => null, 'pages' => 0, 'offset' => 0, 'postType' => 'post', 'order' => 'desc', 'orderBy' => 'date', 'author' => '', 'search' => '', 'exclude' => array( ), 'sticky' => '', 'inherit' => true, 'taxQuery' => null, 'parents' => array( ), 'format' => array( ) ) ), 'tagName' => array( 'type' => 'string', 'default' => 'div' ), 'namespace' => array( 'type' => 'string' ), 'enhancedPagination' => array( 'type' => 'boolean', 'default' => false ) ), 'usesContext' => array( 'templateSlug' ), 'providesContext' => array( 'queryId' => 'queryId', 'query' => 'query', 'displayLayout' => 'displayLayout', 'enhancedPagination' => 'enhancedPagination' ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'html' => false, 'layout' => true, 'interactivity' => true ), 'editorStyle' => 'wp-block-query-editor' ), 'query-no-results' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/query-no-results', 'title' => 'No Results', 'category' => 'theme', 'description' => 'Contains the block elements used to render content when no query results are found.', 'ancestor' => array( 'core/query' ), 'textdomain' => 'default', 'usesContext' => array( 'queryId', 'query' ), 'supports' => array( 'align' => true, 'reusable' => false, 'html' => false, 'color' => array( 'gradients' => true, 'link' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ) ), 'query-pagination' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/query-pagination', 'title' => 'Pagination', 'category' => 'theme', 'ancestor' => array( 'core/query' ), 'allowedBlocks' => array( 'core/query-pagination-previous', 'core/query-pagination-numbers', 'core/query-pagination-next' ), 'description' => 'Displays a paginated navigation to next/previous set of posts, when applicable.', 'textdomain' => 'default', 'attributes' => array( 'paginationArrow' => array( 'type' => 'string', 'default' => 'none' ), 'showLabel' => array( 'type' => 'boolean', 'default' => true ) ), 'usesContext' => array( 'queryId', 'query' ), 'providesContext' => array( 'paginationArrow' => 'paginationArrow', 'showLabel' => 'showLabel' ), 'supports' => array( 'align' => true, 'reusable' => false, 'html' => false, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'layout' => array( 'allowSwitching' => false, 'allowInheriting' => false, 'default' => array( 'type' => 'flex' ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-query-pagination-editor', 'style' => 'wp-block-query-pagination' ), 'query-pagination-next' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/query-pagination-next', 'title' => 'Next Page', 'category' => 'theme', 'parent' => array( 'core/query-pagination' ), 'description' => 'Displays the next posts page link.', 'textdomain' => 'default', 'attributes' => array( 'label' => array( 'type' => 'string' ) ), 'usesContext' => array( 'queryId', 'query', 'paginationArrow', 'showLabel', 'enhancedPagination' ), 'supports' => array( 'reusable' => false, 'html' => false, 'color' => array( 'gradients' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ) ), 'query-pagination-numbers' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/query-pagination-numbers', 'title' => 'Page Numbers', 'category' => 'theme', 'parent' => array( 'core/query-pagination' ), 'description' => 'Displays a list of page numbers for pagination.', 'textdomain' => 'default', 'attributes' => array( 'midSize' => array( 'type' => 'number', 'default' => 2 ) ), 'usesContext' => array( 'queryId', 'query', 'enhancedPagination' ), 'supports' => array( 'reusable' => false, 'html' => false, 'color' => array( 'gradients' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-query-pagination-numbers-editor' ), 'query-pagination-previous' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/query-pagination-previous', 'title' => 'Previous Page', 'category' => 'theme', 'parent' => array( 'core/query-pagination' ), 'description' => 'Displays the previous posts page link.', 'textdomain' => 'default', 'attributes' => array( 'label' => array( 'type' => 'string' ) ), 'usesContext' => array( 'queryId', 'query', 'paginationArrow', 'showLabel', 'enhancedPagination' ), 'supports' => array( 'reusable' => false, 'html' => false, 'color' => array( 'gradients' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ) ), 'query-title' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/query-title', 'title' => 'Query Title', 'category' => 'theme', 'description' => 'Display the query title.', 'textdomain' => 'default', 'attributes' => array( 'type' => array( 'type' => 'string' ), 'textAlign' => array( 'type' => 'string' ), 'level' => array( 'type' => 'number', 'default' => 1 ), 'levelOptions' => array( 'type' => 'array' ), 'showPrefix' => array( 'type' => 'boolean', 'default' => true ), 'showSearchTerm' => array( 'type' => 'boolean', 'default' => true ) ), 'example' => array( 'attributes' => array( 'type' => 'search' ) ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'html' => false, 'color' => array( 'gradients' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'style' => 'wp-block-query-title' ), 'query-total' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/query-total', 'title' => 'Query Total', 'category' => 'theme', 'ancestor' => array( 'core/query' ), 'description' => 'Display the total number of results in a query.', 'textdomain' => 'default', 'attributes' => array( 'displayType' => array( 'type' => 'string', 'default' => 'total-results' ) ), 'usesContext' => array( 'queryId', 'query' ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'html' => false, 'spacing' => array( 'margin' => true, 'padding' => true ), 'color' => array( 'gradients' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'style' => 'wp-block-query-total' ), 'quote' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/quote', 'title' => 'Quote', 'category' => 'text', 'description' => 'Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Julio Cortázar', 'keywords' => array( 'blockquote', 'cite' ), 'textdomain' => 'default', 'attributes' => array( 'value' => array( 'type' => 'string', 'source' => 'html', 'selector' => 'blockquote', 'multiline' => 'p', 'default' => '', 'role' => 'content' ), 'citation' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'cite', 'role' => 'content' ), 'textAlign' => array( 'type' => 'string' ) ), 'supports' => array( 'anchor' => true, 'align' => array( 'left', 'right', 'wide', 'full' ), 'html' => false, 'background' => array( 'backgroundImage' => true, 'backgroundSize' => true, '__experimentalDefaultControls' => array( 'backgroundImage' => true ) ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'style' => true, 'width' => true ) ), 'dimensions' => array( 'minHeight' => true, '__experimentalDefaultControls' => array( 'minHeight' => false ) ), '__experimentalOnEnter' => true, '__experimentalOnMerge' => true, 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'color' => array( 'gradients' => true, 'heading' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'layout' => array( 'allowEditing' => false ), 'spacing' => array( 'blockGap' => true, 'padding' => true, 'margin' => true ), 'interactivity' => array( 'clientNavigation' => true ) ), 'styles' => array( array( 'name' => 'default', 'label' => 'Default', 'isDefault' => true ), array( 'name' => 'plain', 'label' => 'Plain' ) ), 'editorStyle' => 'wp-block-quote-editor', 'style' => 'wp-block-quote' ), 'read-more' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/read-more', 'title' => 'Read More', 'category' => 'theme', 'description' => 'Displays the link of a post, page, or any other content-type.', 'textdomain' => 'default', 'attributes' => array( 'content' => array( 'type' => 'string' ), 'linkTarget' => array( 'type' => 'string', 'default' => '_self' ) ), 'usesContext' => array( 'postId' ), 'supports' => array( 'html' => false, 'color' => array( 'gradients' => true, 'text' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalLetterSpacing' => true, '__experimentalTextDecoration' => true, '__experimentalDefaultControls' => array( 'fontSize' => true, 'textDecoration' => true ) ), 'spacing' => array( 'margin' => array( 'top', 'bottom' ), 'padding' => true, '__experimentalDefaultControls' => array( 'padding' => true ) ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'width' => true, '__experimentalDefaultControls' => array( 'width' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'style' => 'wp-block-read-more' ), 'rss' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/rss', 'title' => 'RSS', 'category' => 'widgets', 'description' => 'Display entries from any RSS or Atom feed.', 'keywords' => array( 'atom', 'feed' ), 'textdomain' => 'default', 'attributes' => array( 'columns' => array( 'type' => 'number', 'default' => 2 ), 'blockLayout' => array( 'type' => 'string', 'default' => 'list' ), 'feedURL' => array( 'type' => 'string', 'default' => '' ), 'itemsToShow' => array( 'type' => 'number', 'default' => 5 ), 'displayExcerpt' => array( 'type' => 'boolean', 'default' => false ), 'displayAuthor' => array( 'type' => 'boolean', 'default' => false ), 'displayDate' => array( 'type' => 'boolean', 'default' => false ), 'excerptLength' => array( 'type' => 'number', 'default' => 55 ) ), 'supports' => array( 'align' => true, 'html' => false, 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'padding' => false, 'margin' => false ) ), 'color' => array( 'background' => true, 'text' => true, 'gradients' => true, 'link' => true ) ), 'editorStyle' => 'wp-block-rss-editor', 'style' => 'wp-block-rss' ), 'search' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/search', 'title' => 'Search', 'category' => 'widgets', 'description' => 'Help visitors find your content.', 'keywords' => array( 'find' ), 'textdomain' => 'default', 'attributes' => array( 'label' => array( 'type' => 'string', 'role' => 'content' ), 'showLabel' => array( 'type' => 'boolean', 'default' => true ), 'placeholder' => array( 'type' => 'string', 'default' => '', 'role' => 'content' ), 'width' => array( 'type' => 'number' ), 'widthUnit' => array( 'type' => 'string' ), 'buttonText' => array( 'type' => 'string', 'role' => 'content' ), 'buttonPosition' => array( 'type' => 'string', 'default' => 'button-outside' ), 'buttonUseIcon' => array( 'type' => 'boolean', 'default' => false ), 'query' => array( 'type' => 'object', 'default' => array( ) ), 'isSearchFieldHidden' => array( 'type' => 'boolean', 'default' => false ) ), 'supports' => array( 'align' => array( 'left', 'center', 'right' ), 'color' => array( 'gradients' => true, '__experimentalSkipSerialization' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'interactivity' => true, 'typography' => array( '__experimentalSkipSerialization' => true, '__experimentalSelector' => '.wp-block-search__label, .wp-block-search__input, .wp-block-search__button', 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( 'color' => true, 'radius' => true, 'width' => true, '__experimentalSkipSerialization' => true, '__experimentalDefaultControls' => array( 'color' => true, 'radius' => true, 'width' => true ) ), 'spacing' => array( 'margin' => true ), 'html' => false ), 'editorStyle' => 'wp-block-search-editor', 'style' => 'wp-block-search' ), 'separator' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/separator', 'title' => 'Separator', 'category' => 'design', 'description' => 'Create a break between ideas or sections with a horizontal separator.', 'keywords' => array( 'horizontal-line', 'hr', 'divider' ), 'textdomain' => 'default', 'attributes' => array( 'opacity' => array( 'type' => 'string', 'default' => 'alpha-channel' ), 'tagName' => array( 'type' => 'string', 'enum' => array( 'hr', 'div' ), 'default' => 'hr' ) ), 'supports' => array( 'anchor' => true, 'align' => array( 'center', 'wide', 'full' ), 'color' => array( 'enableContrastChecker' => false, '__experimentalSkipSerialization' => true, 'gradients' => true, 'background' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => true ) ), 'spacing' => array( 'margin' => array( 'top', 'bottom' ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'styles' => array( array( 'name' => 'default', 'label' => 'Default', 'isDefault' => true ), array( 'name' => 'wide', 'label' => 'Wide Line' ), array( 'name' => 'dots', 'label' => 'Dots' ) ), 'editorStyle' => 'wp-block-separator-editor', 'style' => 'wp-block-separator' ), 'shortcode' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/shortcode', 'title' => 'Shortcode', 'category' => 'widgets', 'description' => 'Insert additional custom elements with a WordPress shortcode.', 'textdomain' => 'default', 'attributes' => array( 'text' => array( 'type' => 'string', 'source' => 'raw' ) ), 'supports' => array( 'className' => false, 'customClassName' => false, 'html' => false ), 'editorStyle' => 'wp-block-shortcode-editor' ), 'site-logo' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/site-logo', 'title' => 'Site Logo', 'category' => 'theme', 'description' => 'Display an image to represent this site. Update this block and the changes apply everywhere.', 'textdomain' => 'default', 'attributes' => array( 'width' => array( 'type' => 'number' ), 'isLink' => array( 'type' => 'boolean', 'default' => true, 'role' => 'content' ), 'linkTarget' => array( 'type' => 'string', 'default' => '_self', 'role' => 'content' ), 'shouldSyncIcon' => array( 'type' => 'boolean' ) ), 'example' => array( 'viewportWidth' => 500, 'attributes' => array( 'width' => 350, 'className' => 'block-editor-block-types-list__site-logo-example' ) ), 'supports' => array( 'html' => false, 'align' => true, 'alignWide' => false, 'color' => array( '__experimentalDuotone' => 'img, .components-placeholder__illustration, .components-placeholder::before', 'text' => false, 'background' => false ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'styles' => array( array( 'name' => 'default', 'label' => 'Default', 'isDefault' => true ), array( 'name' => 'rounded', 'label' => 'Rounded' ) ), 'editorStyle' => 'wp-block-site-logo-editor', 'style' => 'wp-block-site-logo' ), 'site-tagline' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/site-tagline', 'title' => 'Site Tagline', 'category' => 'theme', 'description' => 'Describe in a few words what the site is about. The tagline can be used in search results or when sharing on social networks even if it’s not displayed in the theme design.', 'keywords' => array( 'description' ), 'textdomain' => 'default', 'attributes' => array( 'textAlign' => array( 'type' => 'string' ), 'level' => array( 'type' => 'number', 'default' => 0 ), 'levelOptions' => array( 'type' => 'array', 'default' => array( 0, 1, 2, 3, 4, 5, 6 ) ) ), 'example' => array( 'viewportWidth' => 350, 'attributes' => array( 'textAlign' => 'center' ) ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'html' => false, 'color' => array( 'gradients' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalWritingMode' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ), 'editorStyle' => 'wp-block-site-tagline-editor', 'style' => 'wp-block-site-tagline' ), 'site-title' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/site-title', 'title' => 'Site Title', 'category' => 'theme', 'description' => 'Displays the name of this site. Update the block, and the changes apply everywhere it’s used. This will also appear in the browser title bar and in search results.', 'textdomain' => 'default', 'attributes' => array( 'level' => array( 'type' => 'number', 'default' => 1 ), 'levelOptions' => array( 'type' => 'array', 'default' => array( 0, 1, 2, 3, 4, 5, 6 ) ), 'textAlign' => array( 'type' => 'string' ), 'isLink' => array( 'type' => 'boolean', 'default' => true, 'role' => 'content' ), 'linkTarget' => array( 'type' => 'string', 'default' => '_self', 'role' => 'content' ) ), 'example' => array( 'viewportWidth' => 500 ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'html' => false, 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true, 'link' => true ) ), 'spacing' => array( 'padding' => true, 'margin' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalWritingMode' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ), 'editorStyle' => 'wp-block-site-title-editor', 'style' => 'wp-block-site-title' ), 'social-link' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/social-link', 'title' => 'Social Icon', 'category' => 'widgets', 'parent' => array( 'core/social-links' ), 'description' => 'Display an icon linking to a social profile or site.', 'textdomain' => 'default', 'attributes' => array( 'url' => array( 'type' => 'string', 'role' => 'content' ), 'service' => array( 'type' => 'string' ), 'label' => array( 'type' => 'string', 'role' => 'content' ), 'rel' => array( 'type' => 'string' ) ), 'usesContext' => array( 'openInNewTab', 'showLabels', 'iconColor', 'iconColorValue', 'iconBackgroundColor', 'iconBackgroundColorValue' ), 'supports' => array( 'reusable' => false, 'html' => false, 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-social-link-editor' ), 'social-links' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/social-links', 'title' => 'Social Icons', 'category' => 'widgets', 'allowedBlocks' => array( 'core/social-link' ), 'description' => 'Display icons linking to your social profiles or sites.', 'keywords' => array( 'links' ), 'textdomain' => 'default', 'attributes' => array( 'iconColor' => array( 'type' => 'string' ), 'customIconColor' => array( 'type' => 'string' ), 'iconColorValue' => array( 'type' => 'string' ), 'iconBackgroundColor' => array( 'type' => 'string' ), 'customIconBackgroundColor' => array( 'type' => 'string' ), 'iconBackgroundColorValue' => array( 'type' => 'string' ), 'openInNewTab' => array( 'type' => 'boolean', 'default' => false ), 'showLabels' => array( 'type' => 'boolean', 'default' => false ), 'size' => array( 'type' => 'string' ) ), 'providesContext' => array( 'openInNewTab' => 'openInNewTab', 'showLabels' => 'showLabels', 'iconColor' => 'iconColor', 'iconColorValue' => 'iconColorValue', 'iconBackgroundColor' => 'iconBackgroundColor', 'iconBackgroundColorValue' => 'iconBackgroundColorValue' ), 'supports' => array( 'align' => array( 'left', 'center', 'right' ), 'anchor' => true, '__experimentalExposeControlsToChildren' => true, 'layout' => array( 'allowSwitching' => false, 'allowInheriting' => false, 'allowVerticalAlignment' => false, 'default' => array( 'type' => 'flex' ) ), 'color' => array( 'enableContrastChecker' => false, 'background' => true, 'gradients' => true, 'text' => false, '__experimentalDefaultControls' => array( 'background' => false ) ), 'spacing' => array( 'blockGap' => array( 'horizontal', 'vertical' ), 'margin' => true, 'padding' => true, 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ), '__experimentalDefaultControls' => array( 'blockGap' => true, 'margin' => true, 'padding' => false ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'styles' => array( array( 'name' => 'default', 'label' => 'Default', 'isDefault' => true ), array( 'name' => 'logos-only', 'label' => 'Logos Only' ), array( 'name' => 'pill-shape', 'label' => 'Pill Shape' ) ), 'editorStyle' => 'wp-block-social-links-editor', 'style' => 'wp-block-social-links' ), 'spacer' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/spacer', 'title' => 'Spacer', 'category' => 'design', 'description' => 'Add white space between blocks and customize its height.', 'textdomain' => 'default', 'attributes' => array( 'height' => array( 'type' => 'string', 'default' => '100px' ), 'width' => array( 'type' => 'string' ) ), 'usesContext' => array( 'orientation' ), 'supports' => array( 'anchor' => true, 'spacing' => array( 'margin' => array( 'top', 'bottom' ), '__experimentalDefaultControls' => array( 'margin' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-spacer-editor', 'style' => 'wp-block-spacer' ), 'table' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/table', 'title' => 'Table', 'category' => 'text', 'description' => 'Create structured content in rows and columns to display information.', 'textdomain' => 'default', 'attributes' => array( 'hasFixedLayout' => array( 'type' => 'boolean', 'default' => true ), 'caption' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'figcaption' ), 'head' => array( 'type' => 'array', 'default' => array( ), 'source' => 'query', 'selector' => 'thead tr', 'query' => array( 'cells' => array( 'type' => 'array', 'default' => array( ), 'source' => 'query', 'selector' => 'td,th', 'query' => array( 'content' => array( 'type' => 'rich-text', 'source' => 'rich-text' ), 'tag' => array( 'type' => 'string', 'default' => 'td', 'source' => 'tag' ), 'scope' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'scope' ), 'align' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'data-align' ), 'colspan' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'colspan' ), 'rowspan' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'rowspan' ) ) ) ) ), 'body' => array( 'type' => 'array', 'default' => array( ), 'source' => 'query', 'selector' => 'tbody tr', 'query' => array( 'cells' => array( 'type' => 'array', 'default' => array( ), 'source' => 'query', 'selector' => 'td,th', 'query' => array( 'content' => array( 'type' => 'rich-text', 'source' => 'rich-text' ), 'tag' => array( 'type' => 'string', 'default' => 'td', 'source' => 'tag' ), 'scope' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'scope' ), 'align' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'data-align' ), 'colspan' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'colspan' ), 'rowspan' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'rowspan' ) ) ) ) ), 'foot' => array( 'type' => 'array', 'default' => array( ), 'source' => 'query', 'selector' => 'tfoot tr', 'query' => array( 'cells' => array( 'type' => 'array', 'default' => array( ), 'source' => 'query', 'selector' => 'td,th', 'query' => array( 'content' => array( 'type' => 'rich-text', 'source' => 'rich-text' ), 'tag' => array( 'type' => 'string', 'default' => 'td', 'source' => 'tag' ), 'scope' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'scope' ), 'align' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'data-align' ), 'colspan' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'colspan' ), 'rowspan' => array( 'type' => 'string', 'source' => 'attribute', 'attribute' => 'rowspan' ) ) ) ) ) ), 'supports' => array( 'anchor' => true, 'align' => true, 'color' => array( '__experimentalSkipSerialization' => true, 'gradients' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), '__experimentalBorder' => array( '__experimentalSkipSerialization' => true, 'color' => true, 'style' => true, 'width' => true, '__experimentalDefaultControls' => array( 'color' => true, 'style' => true, 'width' => true ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'selectors' => array( 'root' => '.wp-block-table > table', 'spacing' => '.wp-block-table' ), 'styles' => array( array( 'name' => 'regular', 'label' => 'Default', 'isDefault' => true ), array( 'name' => 'stripes', 'label' => 'Stripes' ) ), 'editorStyle' => 'wp-block-table-editor', 'style' => 'wp-block-table' ), 'tag-cloud' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/tag-cloud', 'title' => 'Tag Cloud', 'category' => 'widgets', 'description' => 'A cloud of popular keywords, each sized by how often it appears.', 'textdomain' => 'default', 'attributes' => array( 'numberOfTags' => array( 'type' => 'number', 'default' => 45, 'minimum' => 1, 'maximum' => 100 ), 'taxonomy' => array( 'type' => 'string', 'default' => 'post_tag' ), 'showTagCounts' => array( 'type' => 'boolean', 'default' => false ), 'smallestFontSize' => array( 'type' => 'string', 'default' => '8pt' ), 'largestFontSize' => array( 'type' => 'string', 'default' => '22pt' ) ), 'styles' => array( array( 'name' => 'default', 'label' => 'Default', 'isDefault' => true ), array( 'name' => 'outline', 'label' => 'Outline' ) ), 'supports' => array( 'html' => false, 'align' => true, 'spacing' => array( 'margin' => true, 'padding' => true ), 'typography' => array( 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalLetterSpacing' => true ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ), 'editorStyle' => 'wp-block-tag-cloud-editor' ), 'template-part' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/template-part', 'title' => 'Template Part', 'category' => 'theme', 'description' => 'Edit the different global regions of your site, like the header, footer, sidebar, or create your own.', 'textdomain' => 'default', 'attributes' => array( 'slug' => array( 'type' => 'string' ), 'theme' => array( 'type' => 'string' ), 'tagName' => array( 'type' => 'string' ), 'area' => array( 'type' => 'string' ) ), 'supports' => array( 'align' => true, 'html' => false, 'reusable' => false, 'renaming' => false, 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-template-part-editor' ), 'term-description' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/term-description', 'title' => 'Term Description', 'category' => 'theme', 'description' => 'Display the description of categories, tags and custom taxonomies when viewing an archive.', 'textdomain' => 'default', 'attributes' => array( 'textAlign' => array( 'type' => 'string' ) ), 'supports' => array( 'align' => array( 'wide', 'full' ), 'html' => false, 'color' => array( 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'spacing' => array( 'padding' => true, 'margin' => true ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true, '__experimentalFontStyle' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'interactivity' => array( 'clientNavigation' => true ), '__experimentalBorder' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true, '__experimentalDefaultControls' => array( 'radius' => true, 'color' => true, 'width' => true, 'style' => true ) ) ) ), 'text-columns' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/text-columns', 'title' => 'Text Columns (deprecated)', 'icon' => 'columns', 'category' => 'design', 'description' => 'This block is deprecated. Please use the Columns block instead.', 'textdomain' => 'default', 'attributes' => array( 'content' => array( 'type' => 'array', 'source' => 'query', 'selector' => 'p', 'query' => array( 'children' => array( 'type' => 'string', 'source' => 'html' ) ), 'default' => array( array( ), array( ) ) ), 'columns' => array( 'type' => 'number', 'default' => 2 ), 'width' => array( 'type' => 'string' ) ), 'supports' => array( 'inserter' => false, 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-text-columns-editor', 'style' => 'wp-block-text-columns' ), 'verse' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/verse', 'title' => 'Verse', 'category' => 'text', 'description' => 'Insert poetry. Use special spacing formats. Or quote song lyrics.', 'keywords' => array( 'poetry', 'poem' ), 'textdomain' => 'default', 'attributes' => array( 'content' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'pre', '__unstablePreserveWhiteSpace' => true, 'role' => 'content' ), 'textAlign' => array( 'type' => 'string' ) ), 'supports' => array( 'anchor' => true, 'background' => array( 'backgroundImage' => true, 'backgroundSize' => true, '__experimentalDefaultControls' => array( 'backgroundImage' => true ) ), 'color' => array( 'gradients' => true, 'link' => true, '__experimentalDefaultControls' => array( 'background' => true, 'text' => true ) ), 'dimensions' => array( 'minHeight' => true, '__experimentalDefaultControls' => array( 'minHeight' => false ) ), 'typography' => array( 'fontSize' => true, '__experimentalFontFamily' => true, 'lineHeight' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalWritingMode' => true, '__experimentalDefaultControls' => array( 'fontSize' => true ) ), 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), '__experimentalBorder' => array( 'radius' => true, 'width' => true, 'color' => true, 'style' => true ), 'interactivity' => array( 'clientNavigation' => true ) ), 'style' => 'wp-block-verse', 'editorStyle' => 'wp-block-verse-editor' ), 'video' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/video', 'title' => 'Video', 'category' => 'media', 'description' => 'Embed a video from your media library or upload a new one.', 'keywords' => array( 'movie' ), 'textdomain' => 'default', 'attributes' => array( 'autoplay' => array( 'type' => 'boolean', 'source' => 'attribute', 'selector' => 'video', 'attribute' => 'autoplay' ), 'caption' => array( 'type' => 'rich-text', 'source' => 'rich-text', 'selector' => 'figcaption', 'role' => 'content' ), 'controls' => array( 'type' => 'boolean', 'source' => 'attribute', 'selector' => 'video', 'attribute' => 'controls', 'default' => true ), 'id' => array( 'type' => 'number', 'role' => 'content' ), 'loop' => array( 'type' => 'boolean', 'source' => 'attribute', 'selector' => 'video', 'attribute' => 'loop' ), 'muted' => array( 'type' => 'boolean', 'source' => 'attribute', 'selector' => 'video', 'attribute' => 'muted' ), 'poster' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'video', 'attribute' => 'poster' ), 'preload' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'video', 'attribute' => 'preload', 'default' => 'metadata' ), 'blob' => array( 'type' => 'string', 'role' => 'local' ), 'src' => array( 'type' => 'string', 'source' => 'attribute', 'selector' => 'video', 'attribute' => 'src', 'role' => 'content' ), 'playsInline' => array( 'type' => 'boolean', 'source' => 'attribute', 'selector' => 'video', 'attribute' => 'playsinline' ), 'tracks' => array( 'role' => 'content', 'type' => 'array', 'items' => array( 'type' => 'object' ), 'default' => array( ) ) ), 'supports' => array( 'anchor' => true, 'align' => true, 'spacing' => array( 'margin' => true, 'padding' => true, '__experimentalDefaultControls' => array( 'margin' => false, 'padding' => false ) ), 'interactivity' => array( 'clientNavigation' => true ) ), 'editorStyle' => 'wp-block-video-editor', 'style' => 'wp-block-video' ), 'widget-group' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/widget-group', 'title' => 'Widget Group', 'category' => 'widgets', 'attributes' => array( 'title' => array( 'type' => 'string' ) ), 'supports' => array( 'html' => false, 'inserter' => true, 'customClassName' => true, 'reusable' => false ), 'editorStyle' => 'wp-block-widget-group-editor', 'style' => 'wp-block-widget-group' ) );PKt\ o post-featured-image/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/post-featured-image", "title": "Featured Image", "category": "theme", "description": "Display a post's featured image.", "textdomain": "default", "attributes": { "isLink": { "type": "boolean", "default": false, "role": "content" }, "aspectRatio": { "type": "string" }, "width": { "type": "string" }, "height": { "type": "string" }, "scale": { "type": "string", "default": "cover" }, "sizeSlug": { "type": "string" }, "rel": { "type": "string", "attribute": "rel", "default": "", "role": "content" }, "linkTarget": { "type": "string", "default": "_self", "role": "content" }, "overlayColor": { "type": "string" }, "customOverlayColor": { "type": "string" }, "dimRatio": { "type": "number", "default": 0 }, "gradient": { "type": "string" }, "customGradient": { "type": "string" }, "useFirstImageFromPost": { "type": "boolean", "default": false } }, "usesContext": [ "postId", "postType", "queryId" ], "example": { "viewportWidth": 350 }, "supports": { "align": [ "left", "right", "center", "wide", "full" ], "color": { "text": false, "background": false }, "__experimentalBorder": { "color": true, "radius": true, "width": true, "__experimentalSkipSerialization": true, "__experimentalDefaultControls": { "color": true, "radius": true, "width": true } }, "filter": { "duotone": true }, "shadow": { "__experimentalSkipSerialization": true }, "html": false, "spacing": { "margin": true, "padding": true }, "interactivity": { "clientNavigation": true } }, "selectors": { "border": ".wp-block-post-featured-image img, .wp-block-post-featured-image .block-editor-media-placeholder, .wp-block-post-featured-image .wp-block-post-featured-image__overlay", "shadow": ".wp-block-post-featured-image img, .wp-block-post-featured-image .components-placeholder", "filter": { "duotone": ".wp-block-post-featured-image img, .wp-block-post-featured-image .wp-block-post-featured-image__placeholder, .wp-block-post-featured-image .components-placeholder__illustration, .wp-block-post-featured-image .components-placeholder::before" } }, "editorStyle": "wp-block-post-featured-image-editor", "style": "wp-block-post-featured-image" } PKt\ؚ"post-featured-image/editor.min.cssnu[.wp-block-post-featured-image .block-editor-media-placeholder{-webkit-backdrop-filter:none;backdrop-filter:none;z-index:1}.wp-block-post-featured-image .components-placeholder,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder{align-items:center;display:flex;justify-content:center;min-height:200px;padding:0}.wp-block-post-featured-image .components-placeholder .components-form-file-upload,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-form-file-upload{display:none}.wp-block-post-featured-image .components-placeholder .components-button,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-button{align-items:center;background:var(--wp-admin-theme-color);border-color:var(--wp-admin-theme-color);border-radius:50%;border-style:solid;color:#fff;display:flex;height:48px;justify-content:center;margin:auto;padding:0;position:relative;width:48px}.wp-block-post-featured-image .components-placeholder .components-button>svg,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-button>svg{color:inherit}.wp-block-post-featured-image .components-placeholder:where(.has-border-color),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where(.has-border-color),.wp-block-post-featured-image img:where(.has-border-color){border-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-top-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-top-color]),.wp-block-post-featured-image img:where([style*=border-top-color]){border-top-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-right-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-right-color]),.wp-block-post-featured-image img:where([style*=border-right-color]){border-right-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-bottom-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-bottom-color]),.wp-block-post-featured-image img:where([style*=border-bottom-color]){border-bottom-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-left-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-left-color]),.wp-block-post-featured-image img:where([style*=border-left-color]){border-left-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-width]),.wp-block-post-featured-image img:where([style*=border-width]){border-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-top-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-top-width]),.wp-block-post-featured-image img:where([style*=border-top-width]){border-top-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-right-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-right-width]),.wp-block-post-featured-image img:where([style*=border-right-width]){border-right-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-bottom-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-bottom-width]),.wp-block-post-featured-image img:where([style*=border-bottom-width]){border-bottom-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-left-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-left-width]),.wp-block-post-featured-image img:where([style*=border-left-width]){border-left-style:solid}.wp-block-post-featured-image[style*=height] .components-placeholder{height:100%;min-height:48px;min-width:48px;width:100%}.wp-block-post-featured-image>a{pointer-events:none}.wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-button,.wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-placeholder__instructions,.wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-placeholder__label{opacity:1;pointer-events:auto}.wp-block-post-featured-image.is-transient{position:relative}.wp-block-post-featured-image.is-transient img{opacity:.3}.wp-block-post-featured-image.is-transient .components-spinner{left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}div[data-type="core/post-featured-image"] img{display:block;height:auto;max-width:100%}PKt\[A))!post-featured-image/style.min.cssnu[.wp-block-post-featured-image{margin-left:0;margin-right:0}.wp-block-post-featured-image a{display:block;height:100%}.wp-block-post-featured-image :where(img){box-sizing:border-box;height:auto;max-width:100%;vertical-align:bottom;width:100%}.wp-block-post-featured-image.alignfull img,.wp-block-post-featured-image.alignwide img{width:100%}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim{background-color:#000;inset:0;position:absolute}.wp-block-post-featured-image{position:relative}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-gradient{background-color:initial}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-0{opacity:0}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-10{opacity:.1}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-20{opacity:.2}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-30{opacity:.3}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-40{opacity:.4}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-50{opacity:.5}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-60{opacity:.6}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-70{opacity:.7}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-80{opacity:.8}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-90{opacity:.9}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-100{opacity:1}.wp-block-post-featured-image:where(.alignleft,.alignright){width:100%}PKt\Zt!post-featured-image/style-rtl.cssnu[.wp-block-post-featured-image{ margin-left:0; margin-right:0; } .wp-block-post-featured-image a{ display:block; height:100%; } .wp-block-post-featured-image :where(img){ box-sizing:border-box; height:auto; max-width:100%; vertical-align:bottom; width:100%; } .wp-block-post-featured-image.alignfull img,.wp-block-post-featured-image.alignwide img{ width:100%; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim{ background-color:#000; inset:0; position:absolute; } .wp-block-post-featured-image{ position:relative; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-gradient{ background-color:initial; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-0{ opacity:0; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-10{ opacity:.1; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-20{ opacity:.2; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-30{ opacity:.3; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-40{ opacity:.4; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-50{ opacity:.5; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-60{ opacity:.6; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-70{ opacity:.7; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-80{ opacity:.8; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-90{ opacity:.9; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-100{ opacity:1; } .wp-block-post-featured-image:where(.alignleft,.alignright){ width:100%; }PKt\i]_"post-featured-image/editor-rtl.cssnu[.wp-block-post-featured-image .block-editor-media-placeholder{ -webkit-backdrop-filter:none; backdrop-filter:none; z-index:1; } .wp-block-post-featured-image .components-placeholder,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder{ align-items:center; display:flex; justify-content:center; min-height:200px; padding:0; } .wp-block-post-featured-image .components-placeholder .components-form-file-upload,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-form-file-upload{ display:none; } .wp-block-post-featured-image .components-placeholder .components-button,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-button{ align-items:center; background:var(--wp-admin-theme-color); border-color:var(--wp-admin-theme-color); border-radius:50%; border-style:solid; color:#fff; display:flex; height:48px; justify-content:center; margin:auto; padding:0; position:relative; width:48px; } .wp-block-post-featured-image .components-placeholder .components-button>svg,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-button>svg{ color:inherit; } .wp-block-post-featured-image .components-placeholder:where(.has-border-color),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where(.has-border-color),.wp-block-post-featured-image img:where(.has-border-color){ border-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-top-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-top-color]),.wp-block-post-featured-image img:where([style*=border-top-color]){ border-top-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-right-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-right-color]),.wp-block-post-featured-image img:where([style*=border-right-color]){ border-left-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-bottom-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-bottom-color]),.wp-block-post-featured-image img:where([style*=border-bottom-color]){ border-bottom-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-left-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-left-color]),.wp-block-post-featured-image img:where([style*=border-left-color]){ border-right-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-width]),.wp-block-post-featured-image img:where([style*=border-width]){ border-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-top-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-top-width]),.wp-block-post-featured-image img:where([style*=border-top-width]){ border-top-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-right-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-right-width]),.wp-block-post-featured-image img:where([style*=border-right-width]){ border-left-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-bottom-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-bottom-width]),.wp-block-post-featured-image img:where([style*=border-bottom-width]){ border-bottom-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-left-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-left-width]),.wp-block-post-featured-image img:where([style*=border-left-width]){ border-right-style:solid; } .wp-block-post-featured-image[style*=height] .components-placeholder{ height:100%; min-height:48px; min-width:48px; width:100%; } .wp-block-post-featured-image>a{ pointer-events:none; } .wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-button,.wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-placeholder__instructions,.wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-placeholder__label{ opacity:1; pointer-events:auto; } .wp-block-post-featured-image.is-transient{ position:relative; } .wp-block-post-featured-image.is-transient img{ opacity:.3; } .wp-block-post-featured-image.is-transient .components-spinner{ position:absolute; right:50%; top:50%; transform:translate(50%, -50%); } div[data-type="core/post-featured-image"] img{ display:block; height:auto; max-width:100%; }PKt\Ztpost-featured-image/style.cssnu[.wp-block-post-featured-image{ margin-left:0; margin-right:0; } .wp-block-post-featured-image a{ display:block; height:100%; } .wp-block-post-featured-image :where(img){ box-sizing:border-box; height:auto; max-width:100%; vertical-align:bottom; width:100%; } .wp-block-post-featured-image.alignfull img,.wp-block-post-featured-image.alignwide img{ width:100%; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim{ background-color:#000; inset:0; position:absolute; } .wp-block-post-featured-image{ position:relative; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-gradient{ background-color:initial; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-0{ opacity:0; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-10{ opacity:.1; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-20{ opacity:.2; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-30{ opacity:.3; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-40{ opacity:.4; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-50{ opacity:.5; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-60{ opacity:.6; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-70{ opacity:.7; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-80{ opacity:.8; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-90{ opacity:.9; } .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-100{ opacity:1; } .wp-block-post-featured-image:where(.alignleft,.alignright){ width:100%; }PKt\[A))%post-featured-image/style-rtl.min.cssnu[.wp-block-post-featured-image{margin-left:0;margin-right:0}.wp-block-post-featured-image a{display:block;height:100%}.wp-block-post-featured-image :where(img){box-sizing:border-box;height:auto;max-width:100%;vertical-align:bottom;width:100%}.wp-block-post-featured-image.alignfull img,.wp-block-post-featured-image.alignwide img{width:100%}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim{background-color:#000;inset:0;position:absolute}.wp-block-post-featured-image{position:relative}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-gradient{background-color:initial}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-0{opacity:0}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-10{opacity:.1}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-20{opacity:.2}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-30{opacity:.3}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-40{opacity:.4}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-50{opacity:.5}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-60{opacity:.6}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-70{opacity:.7}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-80{opacity:.8}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-90{opacity:.9}.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-100{opacity:1}.wp-block-post-featured-image:where(.alignleft,.alignright){width:100%}PKt\҈F&post-featured-image/editor-rtl.min.cssnu[.wp-block-post-featured-image .block-editor-media-placeholder{-webkit-backdrop-filter:none;backdrop-filter:none;z-index:1}.wp-block-post-featured-image .components-placeholder,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder{align-items:center;display:flex;justify-content:center;min-height:200px;padding:0}.wp-block-post-featured-image .components-placeholder .components-form-file-upload,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-form-file-upload{display:none}.wp-block-post-featured-image .components-placeholder .components-button,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-button{align-items:center;background:var(--wp-admin-theme-color);border-color:var(--wp-admin-theme-color);border-radius:50%;border-style:solid;color:#fff;display:flex;height:48px;justify-content:center;margin:auto;padding:0;position:relative;width:48px}.wp-block-post-featured-image .components-placeholder .components-button>svg,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-button>svg{color:inherit}.wp-block-post-featured-image .components-placeholder:where(.has-border-color),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where(.has-border-color),.wp-block-post-featured-image img:where(.has-border-color){border-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-top-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-top-color]),.wp-block-post-featured-image img:where([style*=border-top-color]){border-top-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-right-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-right-color]),.wp-block-post-featured-image img:where([style*=border-right-color]){border-left-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-bottom-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-bottom-color]),.wp-block-post-featured-image img:where([style*=border-bottom-color]){border-bottom-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-left-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-left-color]),.wp-block-post-featured-image img:where([style*=border-left-color]){border-right-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-width]),.wp-block-post-featured-image img:where([style*=border-width]){border-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-top-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-top-width]),.wp-block-post-featured-image img:where([style*=border-top-width]){border-top-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-right-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-right-width]),.wp-block-post-featured-image img:where([style*=border-right-width]){border-left-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-bottom-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-bottom-width]),.wp-block-post-featured-image img:where([style*=border-bottom-width]){border-bottom-style:solid}.wp-block-post-featured-image .components-placeholder:where([style*=border-left-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-left-width]),.wp-block-post-featured-image img:where([style*=border-left-width]){border-right-style:solid}.wp-block-post-featured-image[style*=height] .components-placeholder{height:100%;min-height:48px;min-width:48px;width:100%}.wp-block-post-featured-image>a{pointer-events:none}.wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-button,.wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-placeholder__instructions,.wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-placeholder__label{opacity:1;pointer-events:auto}.wp-block-post-featured-image.is-transient{position:relative}.wp-block-post-featured-image.is-transient img{opacity:.3}.wp-block-post-featured-image.is-transient .components-spinner{position:absolute;right:50%;top:50%;transform:translate(50%,-50%)}div[data-type="core/post-featured-image"] img{display:block;height:auto;max-width:100%}PKt\3 post-featured-image/editor.cssnu[.wp-block-post-featured-image .block-editor-media-placeholder{ -webkit-backdrop-filter:none; backdrop-filter:none; z-index:1; } .wp-block-post-featured-image .components-placeholder,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder{ align-items:center; display:flex; justify-content:center; min-height:200px; padding:0; } .wp-block-post-featured-image .components-placeholder .components-form-file-upload,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-form-file-upload{ display:none; } .wp-block-post-featured-image .components-placeholder .components-button,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-button{ align-items:center; background:var(--wp-admin-theme-color); border-color:var(--wp-admin-theme-color); border-radius:50%; border-style:solid; color:#fff; display:flex; height:48px; justify-content:center; margin:auto; padding:0; position:relative; width:48px; } .wp-block-post-featured-image .components-placeholder .components-button>svg,.wp-block-post-featured-image .wp-block-post-featured-image__placeholder .components-button>svg{ color:inherit; } .wp-block-post-featured-image .components-placeholder:where(.has-border-color),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where(.has-border-color),.wp-block-post-featured-image img:where(.has-border-color){ border-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-top-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-top-color]),.wp-block-post-featured-image img:where([style*=border-top-color]){ border-top-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-right-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-right-color]),.wp-block-post-featured-image img:where([style*=border-right-color]){ border-right-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-bottom-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-bottom-color]),.wp-block-post-featured-image img:where([style*=border-bottom-color]){ border-bottom-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-left-color]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-left-color]),.wp-block-post-featured-image img:where([style*=border-left-color]){ border-left-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-width]),.wp-block-post-featured-image img:where([style*=border-width]){ border-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-top-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-top-width]),.wp-block-post-featured-image img:where([style*=border-top-width]){ border-top-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-right-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-right-width]),.wp-block-post-featured-image img:where([style*=border-right-width]){ border-right-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-bottom-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-bottom-width]),.wp-block-post-featured-image img:where([style*=border-bottom-width]){ border-bottom-style:solid; } .wp-block-post-featured-image .components-placeholder:where([style*=border-left-width]),.wp-block-post-featured-image .wp-block-post-featured-image__placeholder:where([style*=border-left-width]),.wp-block-post-featured-image img:where([style*=border-left-width]){ border-left-style:solid; } .wp-block-post-featured-image[style*=height] .components-placeholder{ height:100%; min-height:48px; min-width:48px; width:100%; } .wp-block-post-featured-image>a{ pointer-events:none; } .wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-button,.wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-placeholder__instructions,.wp-block-post-featured-image.is-selected .components-placeholder.has-illustration .components-placeholder__label{ opacity:1; pointer-events:auto; } .wp-block-post-featured-image.is-transient{ position:relative; } .wp-block-post-featured-image.is-transient img{ opacity:.3; } .wp-block-post-featured-image.is-transient .components-spinner{ left:50%; position:absolute; top:50%; transform:translate(-50%, -50%); } div[data-type="core/post-featured-image"] img{ display:block; height:auto; max-width:100%; }PKt\Q footnotes.phpnu[context['postId'] ) ) { return ''; } if ( post_password_required( $block->context['postId'] ) ) { return; } $footnotes = get_post_meta( $block->context['postId'], 'footnotes', true ); if ( ! $footnotes ) { return; } $footnotes = json_decode( $footnotes, true ); if ( ! is_array( $footnotes ) || count( $footnotes ) === 0 ) { return ''; } $wrapper_attributes = get_block_wrapper_attributes(); $footnote_index = 1; $block_content = ''; foreach ( $footnotes as $footnote ) { // Translators: %d: Integer representing the number of return links on the page. $aria_label = sprintf( __( 'Jump to footnote reference %1$d' ), $footnote_index ); $block_content .= sprintf( '
        • %2$s ↩︎
        • ', $footnote['id'], $footnote['content'], $aria_label ); ++$footnote_index; } return sprintf( '
            %2$s
          ', $wrapper_attributes, $block_content ); } /** * Registers the `core/footnotes` block on the server. * * @since 6.3.0 */ function register_block_core_footnotes() { register_block_type_from_metadata( __DIR__ . '/footnotes', array( 'render_callback' => 'render_block_core_footnotes', ) ); } add_action( 'init', 'register_block_core_footnotes' ); /** * Registers the footnotes meta field required for footnotes to work. * * @since 6.5.0 */ function register_block_core_footnotes_post_meta() { $post_types = get_post_types( array( 'show_in_rest' => true ) ); foreach ( $post_types as $post_type ) { // Only register the meta field if the post type supports the editor, custom fields, and revisions. if ( post_type_supports( $post_type, 'editor' ) && post_type_supports( $post_type, 'custom-fields' ) && post_type_supports( $post_type, 'revisions' ) ) { register_post_meta( $post_type, 'footnotes', array( 'show_in_rest' => true, 'single' => true, 'type' => 'string', 'revisions_enabled' => true, ) ); } } } /* * Most post types are registered at priority 10, so use priority 20 here in * order to catch them. */ add_action( 'init', 'register_block_core_footnotes_post_meta', 20 ); /** * Adds the footnotes field to the revisions display. * * @since 6.3.0 * * @param array $fields The revision fields. * @return array The revision fields. */ function wp_add_footnotes_to_revision( $fields ) { $fields['footnotes'] = __( 'Footnotes' ); return $fields; } add_filter( '_wp_post_revision_fields', 'wp_add_footnotes_to_revision' ); /** * Gets the footnotes field from the revision for the revisions screen. * * @since 6.3.0 * * @param string $revision_field The field value, but $revision->$field * (footnotes) does not exist. * @param string $field The field name, in this case "footnotes". * @param object $revision The revision object to compare against. * @return string The field value. */ function wp_get_footnotes_from_revision( $revision_field, $field, $revision ) { return get_metadata( 'post', $revision->ID, $field, true ); } add_filter( '_wp_post_revision_field_footnotes', 'wp_get_footnotes_from_revision', 10, 3 ); PKt\mĨU pattern.phpnu[ 'render_block_core_pattern', ) ); } /** * Renders the `core/pattern` block on the server. * * @since 6.3.0 Backwards compatibility: blocks with no `syncStatus` attribute do not receive block wrapper. * * @global WP_Embed $wp_embed Used to process embedded content within patterns * * @param array $attributes Block attributes. * * @return string Returns the output of the pattern. */ function render_block_core_pattern( $attributes ) { static $seen_refs = array(); if ( empty( $attributes['slug'] ) ) { return ''; } $slug = $attributes['slug']; $registry = WP_Block_Patterns_Registry::get_instance(); if ( ! $registry->is_registered( $slug ) ) { return ''; } if ( isset( $seen_refs[ $attributes['slug'] ] ) ) { // WP_DEBUG_DISPLAY must only be honored when WP_DEBUG. This precedent // is set in `wp_debug_mode()`. $is_debug = WP_DEBUG && WP_DEBUG_DISPLAY; return $is_debug ? // translators: Visible only in the front end, this warning takes the place of a faulty block. %s represents a pattern's slug. sprintf( __( '[block rendering halted for pattern "%s"]' ), $slug ) : ''; } $pattern = $registry->get_registered( $slug ); $content = $pattern['content']; $seen_refs[ $attributes['slug'] ] = true; $content = do_blocks( $content ); global $wp_embed; $content = $wp_embed->autoembed( $content ); unset( $seen_refs[ $attributes['slug'] ] ); return $content; } add_action( 'init', 'register_block_core_pattern' ); PKt\yrpattern/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/pattern", "title": "Pattern Placeholder", "category": "theme", "description": "Show a block pattern.", "supports": { "html": false, "inserter": false, "renaming": false, "interactivity": { "clientNavigation": true } }, "textdomain": "default", "attributes": { "slug": { "type": "string" } } } PKt\DOquote/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/quote", "title": "Quote", "category": "text", "description": "Give quoted text visual emphasis. \"In quoting others, we cite ourselves.\" — Julio Cortázar", "keywords": [ "blockquote", "cite" ], "textdomain": "default", "attributes": { "value": { "type": "string", "source": "html", "selector": "blockquote", "multiline": "p", "default": "", "role": "content" }, "citation": { "type": "rich-text", "source": "rich-text", "selector": "cite", "role": "content" }, "textAlign": { "type": "string" } }, "supports": { "anchor": true, "align": [ "left", "right", "wide", "full" ], "html": false, "background": { "backgroundImage": true, "backgroundSize": true, "__experimentalDefaultControls": { "backgroundImage": true } }, "__experimentalBorder": { "color": true, "radius": true, "style": true, "width": true, "__experimentalDefaultControls": { "color": true, "radius": true, "style": true, "width": true } }, "dimensions": { "minHeight": true, "__experimentalDefaultControls": { "minHeight": false } }, "__experimentalOnEnter": true, "__experimentalOnMerge": true, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "color": { "gradients": true, "heading": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "layout": { "allowEditing": false }, "spacing": { "blockGap": true, "padding": true, "margin": true }, "interactivity": { "clientNavigation": true } }, "styles": [ { "name": "default", "label": "Default", "isDefault": true }, { "name": "plain", "label": "Plain" } ], "editorStyle": "wp-block-quote-editor", "style": "wp-block-quote" } PKt\)r1quote/theme.min.cssnu[.wp-block-quote{border-left:.25em solid;margin:0 0 1.75em;padding-left:1em}.wp-block-quote cite,.wp-block-quote footer{color:currentColor;font-size:.8125em;font-style:normal;position:relative}.wp-block-quote:where(.has-text-align-right){border-left:none;border-right:.25em solid;padding-left:0;padding-right:1em}.wp-block-quote:where(.has-text-align-center){border:none;padding-left:0}.wp-block-quote.is-large,.wp-block-quote.is-style-large,.wp-block-quote:where(.is-style-plain){border:none}PKt\l{ $$quote/theme.cssnu[.wp-block-quote{ border-left:.25em solid; margin:0 0 1.75em; padding-left:1em; } .wp-block-quote cite,.wp-block-quote footer{ color:currentColor; font-size:.8125em; font-style:normal; position:relative; } .wp-block-quote:where(.has-text-align-right){ border-left:none; border-right:.25em solid; padding-left:0; padding-right:1em; } .wp-block-quote:where(.has-text-align-center){ border:none; padding-left:0; } .wp-block-quote.is-large,.wp-block-quote.is-style-large,.wp-block-quote:where(.is-style-plain){ border:none; }PKt\DOquote/style.min.cssnu[.wp-block-quote{box-sizing:border-box;overflow-wrap:break-word}.wp-block-quote.is-large:where(:not(.is-style-plain)),.wp-block-quote.is-style-large:where(:not(.is-style-plain)){margin-bottom:1em;padding:0 1em}.wp-block-quote.is-large:where(:not(.is-style-plain)) p,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) p{font-size:1.5em;font-style:italic;line-height:1.6}.wp-block-quote.is-large:where(:not(.is-style-plain)) cite,.wp-block-quote.is-large:where(:not(.is-style-plain)) footer,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) cite,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) footer{font-size:1.125em;text-align:right}.wp-block-quote>cite{display:block}PKt\꘥Oquote/style-rtl.cssnu[.wp-block-quote{ box-sizing:border-box; overflow-wrap:break-word; } .wp-block-quote.is-large:where(:not(.is-style-plain)),.wp-block-quote.is-style-large:where(:not(.is-style-plain)){ margin-bottom:1em; padding:0 1em; } .wp-block-quote.is-large:where(:not(.is-style-plain)) p,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) p{ font-size:1.5em; font-style:italic; line-height:1.6; } .wp-block-quote.is-large:where(:not(.is-style-plain)) cite,.wp-block-quote.is-large:where(:not(.is-style-plain)) footer,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) cite,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) footer{ font-size:1.125em; text-align:left; } .wp-block-quote>cite{ display:block; }PKt\O''quote/theme-rtl.cssnu[.wp-block-quote{ border-right:.25em solid; margin:0 0 1.75em; padding-right:1em; } .wp-block-quote cite,.wp-block-quote footer{ color:currentColor; font-size:.8125em; font-style:normal; position:relative; } .wp-block-quote:where(.has-text-align-right){ border-left:.25em solid; border-right:none; padding-left:1em; padding-right:0; } .wp-block-quote:where(.has-text-align-center){ border:none; padding-right:0; } .wp-block-quote.is-large,.wp-block-quote.is-style-large,.wp-block-quote:where(.is-style-plain){ border:none; }PKt\4quote/style.cssnu[.wp-block-quote{ box-sizing:border-box; overflow-wrap:break-word; } .wp-block-quote.is-large:where(:not(.is-style-plain)),.wp-block-quote.is-style-large:where(:not(.is-style-plain)){ margin-bottom:1em; padding:0 1em; } .wp-block-quote.is-large:where(:not(.is-style-plain)) p,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) p{ font-size:1.5em; font-style:italic; line-height:1.6; } .wp-block-quote.is-large:where(:not(.is-style-plain)) cite,.wp-block-quote.is-large:where(:not(.is-style-plain)) footer,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) cite,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) footer{ font-size:1.125em; text-align:right; } .wp-block-quote>cite{ display:block; }PKt\'΃quote/theme-rtl.min.cssnu[.wp-block-quote{border-right:.25em solid;margin:0 0 1.75em;padding-right:1em}.wp-block-quote cite,.wp-block-quote footer{color:currentColor;font-size:.8125em;font-style:normal;position:relative}.wp-block-quote:where(.has-text-align-right){border-left:.25em solid;border-right:none;padding-left:1em;padding-right:0}.wp-block-quote:where(.has-text-align-center){border:none;padding-right:0}.wp-block-quote.is-large,.wp-block-quote.is-style-large,.wp-block-quote:where(.is-style-plain){border:none}PKt\/=quote/style-rtl.min.cssnu[.wp-block-quote{box-sizing:border-box;overflow-wrap:break-word}.wp-block-quote.is-large:where(:not(.is-style-plain)),.wp-block-quote.is-style-large:where(:not(.is-style-plain)){margin-bottom:1em;padding:0 1em}.wp-block-quote.is-large:where(:not(.is-style-plain)) p,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) p{font-size:1.5em;font-style:italic;line-height:1.6}.wp-block-quote.is-large:where(:not(.is-style-plain)) cite,.wp-block-quote.is-large:where(:not(.is-style-plain)) footer,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) cite,.wp-block-quote.is-style-large:where(:not(.is-style-plain)) footer{font-size:1.125em;text-align:left}.wp-block-quote>cite{display:block}PKt\~c&&post-navigation-link/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/post-navigation-link", "title": "Post Navigation Link", "category": "theme", "description": "Displays the next or previous post link that is adjacent to the current post.", "textdomain": "default", "attributes": { "textAlign": { "type": "string" }, "type": { "type": "string", "default": "next" }, "label": { "type": "string" }, "showTitle": { "type": "boolean", "default": false }, "linkLabel": { "type": "boolean", "default": false }, "arrow": { "type": "string", "default": "none" }, "taxonomy": { "type": "string", "default": "" } }, "usesContext": [ "postType" ], "supports": { "reusable": false, "html": false, "color": { "link": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalWritingMode": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } }, "style": "wp-block-post-navigation-link" } PKt\(H"post-navigation-link/style.min.cssnu[.wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-previous{display:inline-block;margin-right:1ch}.wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-previous:not(.is-arrow-chevron){transform:scaleX(1)}.wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-next{display:inline-block;margin-left:1ch}.wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-next:not(.is-arrow-chevron){transform:scaleX(1)}.wp-block-post-navigation-link.has-text-align-left[style*="writing-mode: vertical-lr"],.wp-block-post-navigation-link.has-text-align-right[style*="writing-mode: vertical-rl"]{rotate:180deg}PKt\H"post-navigation-link/style-rtl.cssnu[.wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-previous{ display:inline-block; margin-left:1ch; } .wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-previous:not(.is-arrow-chevron){ transform:scaleX(-1);; } .wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-next{ display:inline-block; margin-right:1ch; } .wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-next:not(.is-arrow-chevron){ transform:scaleX(-1);; } .wp-block-post-navigation-link.has-text-align-left[style*="writing-mode: vertical-lr"],.wp-block-post-navigation-link.has-text-align-right[style*="writing-mode: vertical-rl"]{ rotate:180deg; }PKt\Fٱpost-navigation-link/style.cssnu[.wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-previous{ display:inline-block; margin-right:1ch; } .wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-previous:not(.is-arrow-chevron){ transform:scaleX(1); } .wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-next{ display:inline-block; margin-left:1ch; } .wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-next:not(.is-arrow-chevron){ transform:scaleX(1); } .wp-block-post-navigation-link.has-text-align-left[style*="writing-mode: vertical-lr"],.wp-block-post-navigation-link.has-text-align-right[style*="writing-mode: vertical-rl"]{ rotate:180deg; }PKt\B&post-navigation-link/style-rtl.min.cssnu[.wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-previous{display:inline-block;margin-left:1ch}.wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-previous:not(.is-arrow-chevron){transform:scaleX(-1)}.wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-next{display:inline-block;margin-right:1ch}.wp-block-post-navigation-link .wp-block-post-navigation-link__arrow-next:not(.is-arrow-chevron){transform:scaleX(-1)}.wp-block-post-navigation-link.has-text-align-left[style*="writing-mode: vertical-lr"],.wp-block-post-navigation-link.has-text-align-right[style*="writing-mode: vertical-rl"]{rotate:180deg}PKt\\ comment-content.phpnu[context['commentId'] ) ) { return ''; } $comment = get_comment( $block->context['commentId'] ); $commenter = wp_get_current_commenter(); $show_pending_links = isset( $commenter['comment_author'] ) && $commenter['comment_author']; if ( empty( $comment ) ) { return ''; } $args = array(); $comment_text = get_comment_text( $comment, $args ); if ( ! $comment_text ) { return ''; } /** This filter is documented in wp-includes/comment-template.php */ $comment_text = apply_filters( 'comment_text', $comment_text, $comment, $args ); $moderation_note = ''; if ( '0' === $comment->comment_approved ) { $commenter = wp_get_current_commenter(); if ( $commenter['comment_author_email'] ) { $moderation_note = __( 'Your comment is awaiting moderation.' ); } else { $moderation_note = __( 'Your comment is awaiting moderation. This is a preview; your comment will be visible after it has been approved.' ); } $moderation_note = '

          ' . $moderation_note . '

          '; if ( ! $show_pending_links ) { $comment_text = wp_kses( $comment_text, array() ); } } $classes = array(); if ( isset( $attributes['textAlign'] ) ) { $classes[] = 'has-text-align-' . $attributes['textAlign']; } if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { $classes[] = 'has-link-color'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) ); return sprintf( '
          %2$s%3$s
          ', $wrapper_attributes, $moderation_note, $comment_text ); } /** * Registers the `core/comment-content` block on the server. * * @since 6.0.0 */ function register_block_core_comment_content() { register_block_type_from_metadata( __DIR__ . '/comment-content', array( 'render_callback' => 'render_block_core_comment_content', ) ); } add_action( 'init', 'register_block_core_comment_content' ); PKt\Juݫspacer/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/spacer", "title": "Spacer", "category": "design", "description": "Add white space between blocks and customize its height.", "textdomain": "default", "attributes": { "height": { "type": "string", "default": "100px" }, "width": { "type": "string" } }, "usesContext": [ "orientation" ], "supports": { "anchor": true, "spacing": { "margin": [ "top", "bottom" ], "__experimentalDefaultControls": { "margin": true } }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-spacer-editor", "style": "wp-block-spacer" } PKt\(Qyspacer/editor.min.cssnu[.block-editor-block-list__block[data-type="core/spacer"]:before{content:"";display:block;height:100%;min-height:8px;min-width:8px;position:absolute;width:100%;z-index:1}.block-library-spacer__resize-container.has-show-handle,.wp-block-spacer.is-hovered .block-library-spacer__resize-container,.wp-block-spacer.is-selected.custom-sizes-disabled{background:#0000001a}.is-dark-theme .block-library-spacer__resize-container.has-show-handle,.is-dark-theme .wp-block-spacer.is-hovered .block-library-spacer__resize-container,.is-dark-theme .wp-block-spacer.is-selected.custom-sizes-disabled{background:#ffffff26}.block-library-spacer__resize-container{clear:both}.block-library-spacer__resize-container:not(.is-resizing){height:100%!important;width:100%!important}.block-library-spacer__resize-container .components-resizable-box__handle:before{content:none}.block-library-spacer__resize-container.resize-horizontal{height:100%!important;margin-bottom:0}PKt\Tspacer/style.min.cssnu[.wp-block-spacer{clear:both}PKt\`9!!spacer/style-rtl.cssnu[.wp-block-spacer{ clear:both; }PKt\)lspacer/editor-rtl.cssnu[.block-editor-block-list__block[data-type="core/spacer"]:before{ content:""; display:block; height:100%; min-height:8px; min-width:8px; position:absolute; width:100%; z-index:1; } .block-library-spacer__resize-container.has-show-handle,.wp-block-spacer.is-hovered .block-library-spacer__resize-container,.wp-block-spacer.is-selected.custom-sizes-disabled{ background:#0000001a; } .is-dark-theme .block-library-spacer__resize-container.has-show-handle,.is-dark-theme .wp-block-spacer.is-hovered .block-library-spacer__resize-container,.is-dark-theme .wp-block-spacer.is-selected.custom-sizes-disabled{ background:#ffffff26; } .block-library-spacer__resize-container{ clear:both; } .block-library-spacer__resize-container:not(.is-resizing){ height:100% !important; width:100% !important; } .block-library-spacer__resize-container .components-resizable-box__handle:before{ content:none; } .block-library-spacer__resize-container.resize-horizontal{ height:100% !important; margin-bottom:0; }PKt\`9!!spacer/style.cssnu[.wp-block-spacer{ clear:both; }PKt\Tspacer/style-rtl.min.cssnu[.wp-block-spacer{clear:both}PKt\(Qyspacer/editor-rtl.min.cssnu[.block-editor-block-list__block[data-type="core/spacer"]:before{content:"";display:block;height:100%;min-height:8px;min-width:8px;position:absolute;width:100%;z-index:1}.block-library-spacer__resize-container.has-show-handle,.wp-block-spacer.is-hovered .block-library-spacer__resize-container,.wp-block-spacer.is-selected.custom-sizes-disabled{background:#0000001a}.is-dark-theme .block-library-spacer__resize-container.has-show-handle,.is-dark-theme .wp-block-spacer.is-hovered .block-library-spacer__resize-container,.is-dark-theme .wp-block-spacer.is-selected.custom-sizes-disabled{background:#ffffff26}.block-library-spacer__resize-container{clear:both}.block-library-spacer__resize-container:not(.is-resizing){height:100%!important;width:100%!important}.block-library-spacer__resize-container .components-resizable-box__handle:before{content:none}.block-library-spacer__resize-container.resize-horizontal{height:100%!important;margin-bottom:0}PKt\)lspacer/editor.cssnu[.block-editor-block-list__block[data-type="core/spacer"]:before{ content:""; display:block; height:100%; min-height:8px; min-width:8px; position:absolute; width:100%; z-index:1; } .block-library-spacer__resize-container.has-show-handle,.wp-block-spacer.is-hovered .block-library-spacer__resize-container,.wp-block-spacer.is-selected.custom-sizes-disabled{ background:#0000001a; } .is-dark-theme .block-library-spacer__resize-container.has-show-handle,.is-dark-theme .wp-block-spacer.is-hovered .block-library-spacer__resize-container,.is-dark-theme .wp-block-spacer.is-selected.custom-sizes-disabled{ background:#ffffff26; } .block-library-spacer__resize-container{ clear:both; } .block-library-spacer__resize-container:not(.is-resizing){ height:100% !important; width:100% !important; } .block-library-spacer__resize-container .components-resizable-box__handle:before{ content:none; } .block-library-spacer__resize-container.resize-horizontal{ height:100% !important; margin-bottom:0; }PKt\+ cover/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/cover", "title": "Cover", "category": "media", "description": "Add an image or video with a text overlay.", "textdomain": "default", "attributes": { "url": { "type": "string" }, "useFeaturedImage": { "type": "boolean", "default": false }, "id": { "type": "number" }, "alt": { "type": "string", "default": "" }, "hasParallax": { "type": "boolean", "default": false }, "isRepeated": { "type": "boolean", "default": false }, "dimRatio": { "type": "number", "default": 100 }, "overlayColor": { "type": "string" }, "customOverlayColor": { "type": "string" }, "isUserOverlayColor": { "type": "boolean" }, "backgroundType": { "type": "string", "default": "image" }, "focalPoint": { "type": "object" }, "minHeight": { "type": "number" }, "minHeightUnit": { "type": "string" }, "gradient": { "type": "string" }, "customGradient": { "type": "string" }, "contentPosition": { "type": "string" }, "isDark": { "type": "boolean", "default": true }, "allowedBlocks": { "type": "array" }, "templateLock": { "type": [ "string", "boolean" ], "enum": [ "all", "insert", "contentOnly", false ] }, "tagName": { "type": "string", "default": "div" }, "sizeSlug": { "type": "string" } }, "usesContext": [ "postId", "postType" ], "supports": { "anchor": true, "align": true, "html": false, "shadow": true, "spacing": { "padding": true, "margin": [ "top", "bottom" ], "blockGap": true, "__experimentalDefaultControls": { "padding": true, "blockGap": true } }, "__experimentalBorder": { "color": true, "radius": true, "style": true, "width": true, "__experimentalDefaultControls": { "color": true, "radius": true, "style": true, "width": true } }, "color": { "__experimentalDuotone": "> .wp-block-cover__image-background, > .wp-block-cover__video-background", "heading": true, "text": true, "background": false, "__experimentalSkipSerialization": [ "gradients" ], "enableContrastChecker": false }, "dimensions": { "aspectRatio": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "layout": { "allowJustification": false }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-cover-editor", "style": "wp-block-cover" } PKt\]cover/editor.min.cssnu[.wp-block-cover.is-placeholder{align-items:stretch;display:flex;min-height:240px;padding:0!important}.wp-block-cover.is-placeholder .components-placeholder.is-large{justify-content:flex-start;z-index:1}.wp-block-cover.is-placeholder:focus:after{min-height:auto}.wp-block-cover.components-placeholder h2{color:inherit}.wp-block-cover.is-transient{position:relative}.wp-block-cover.is-transient:before{background-color:#fff;content:"";height:100%;opacity:.3;position:absolute;width:100%;z-index:1}.wp-block-cover.is-transient .wp-block-cover__inner-container{z-index:2}.wp-block-cover .components-spinner{left:50%;margin:0;position:absolute;top:50%;transform:translate(-50%,-50%)}.wp-block-cover .wp-block-cover__inner-container{margin-left:0;margin-right:0;text-align:left}.wp-block-cover .wp-block-cover__placeholder-background-options{width:100%}.wp-block-cover .wp-block-cover__image--placeholder-image{bottom:0;left:0;position:absolute;right:0;top:0}[data-align=left]>.wp-block-cover,[data-align=right]>.wp-block-cover{max-width:420px;width:100%}.block-library-cover__reset-button{margin-left:auto}.block-library-cover__resize-container{bottom:0;left:0;min-height:50px;position:absolute!important;right:0;top:0}.components-popover.block-editor-block-popover.block-library-cover__resizable-box-popover .block-library-cover__resize-container,.components-popover.block-editor-block-popover.block-library-cover__resizable-box-popover .components-popover__content>div{overflow:visible;pointer-events:none}.block-editor-block-patterns-list__list-item .has-parallax.wp-block-cover{background-attachment:scroll}.color-block-support-panel__inner-wrapper>:not(.block-editor-tools-panel-color-gradient-settings__item){margin-top:24px}PKt\OyJJcover/style.min.cssnu[.wp-block-cover,.wp-block-cover-image{align-items:center;background-position:50%;box-sizing:border-box;display:flex;justify-content:center;min-height:430px;overflow:hidden;overflow:clip;padding:1em;position:relative}.wp-block-cover .has-background-dim:not([class*=-background-color]),.wp-block-cover-image .has-background-dim:not([class*=-background-color]),.wp-block-cover-image.has-background-dim:not([class*=-background-color]),.wp-block-cover.has-background-dim:not([class*=-background-color]){background-color:#000}.wp-block-cover .has-background-dim.has-background-gradient,.wp-block-cover-image .has-background-dim.has-background-gradient{background-color:initial}.wp-block-cover-image.has-background-dim:before,.wp-block-cover.has-background-dim:before{background-color:inherit;content:""}.wp-block-cover .wp-block-cover__background,.wp-block-cover .wp-block-cover__gradient-background,.wp-block-cover-image .wp-block-cover__background,.wp-block-cover-image .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim:not(.has-background-gradient):before,.wp-block-cover.has-background-dim:not(.has-background-gradient):before{bottom:0;left:0;opacity:.5;position:absolute;right:0;top:0}.wp-block-cover-image.has-background-dim.has-background-dim-10 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-10 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-10:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-10 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-10 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-10:not(.has-background-gradient):before{opacity:.1}.wp-block-cover-image.has-background-dim.has-background-dim-20 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-20 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-20:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-20 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-20 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-20:not(.has-background-gradient):before{opacity:.2}.wp-block-cover-image.has-background-dim.has-background-dim-30 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-30 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-30:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-30 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-30 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-30:not(.has-background-gradient):before{opacity:.3}.wp-block-cover-image.has-background-dim.has-background-dim-40 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-40 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-40:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-40 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-40 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-40:not(.has-background-gradient):before{opacity:.4}.wp-block-cover-image.has-background-dim.has-background-dim-50 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-50 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-50:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-50 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-50 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-50:not(.has-background-gradient):before{opacity:.5}.wp-block-cover-image.has-background-dim.has-background-dim-60 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-60 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-60:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-60 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-60 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-60:not(.has-background-gradient):before{opacity:.6}.wp-block-cover-image.has-background-dim.has-background-dim-70 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-70 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-70:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-70 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-70 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-70:not(.has-background-gradient):before{opacity:.7}.wp-block-cover-image.has-background-dim.has-background-dim-80 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-80 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-80:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-80 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-80 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-80:not(.has-background-gradient):before{opacity:.8}.wp-block-cover-image.has-background-dim.has-background-dim-90 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-90 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-90:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-90 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-90 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-90:not(.has-background-gradient):before{opacity:.9}.wp-block-cover-image.has-background-dim.has-background-dim-100 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-100 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-100:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-100 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-100 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-100:not(.has-background-gradient):before{opacity:1}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-0,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-0,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-0,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-0{opacity:0}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-10,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-10,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-10,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-10{opacity:.1}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-20,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-20,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-20,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-20{opacity:.2}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-30,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-30,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-30,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-30{opacity:.3}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-40,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-40,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-40,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-40{opacity:.4}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-50,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-50,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-50,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-50{opacity:.5}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-60,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-60,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-60,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-60{opacity:.6}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-70,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-70,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-70,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-70{opacity:.7}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-80,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-80,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-80,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-80{opacity:.8}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-90,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-90,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-90,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-90{opacity:.9}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-100,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-100,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-100,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-100{opacity:1}.wp-block-cover-image.alignleft,.wp-block-cover-image.alignright,.wp-block-cover.alignleft,.wp-block-cover.alignright{max-width:420px;width:100%}.wp-block-cover-image.aligncenter,.wp-block-cover-image.alignleft,.wp-block-cover-image.alignright,.wp-block-cover.aligncenter,.wp-block-cover.alignleft,.wp-block-cover.alignright{display:flex}.wp-block-cover .wp-block-cover__inner-container,.wp-block-cover-image .wp-block-cover__inner-container{color:inherit;position:relative;width:100%}.wp-block-cover-image.is-position-top-left,.wp-block-cover.is-position-top-left{align-items:flex-start;justify-content:flex-start}.wp-block-cover-image.is-position-top-center,.wp-block-cover.is-position-top-center{align-items:flex-start;justify-content:center}.wp-block-cover-image.is-position-top-right,.wp-block-cover.is-position-top-right{align-items:flex-start;justify-content:flex-end}.wp-block-cover-image.is-position-center-left,.wp-block-cover.is-position-center-left{align-items:center;justify-content:flex-start}.wp-block-cover-image.is-position-center-center,.wp-block-cover.is-position-center-center{align-items:center;justify-content:center}.wp-block-cover-image.is-position-center-right,.wp-block-cover.is-position-center-right{align-items:center;justify-content:flex-end}.wp-block-cover-image.is-position-bottom-left,.wp-block-cover.is-position-bottom-left{align-items:flex-end;justify-content:flex-start}.wp-block-cover-image.is-position-bottom-center,.wp-block-cover.is-position-bottom-center{align-items:flex-end;justify-content:center}.wp-block-cover-image.is-position-bottom-right,.wp-block-cover.is-position-bottom-right{align-items:flex-end;justify-content:flex-end}.wp-block-cover-image.has-custom-content-position.has-custom-content-position .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position .wp-block-cover__inner-container{margin:0}.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-bottom-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-bottom-right .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-center-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-center-right .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-top-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-top-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-bottom-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-bottom-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-center-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-center-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-top-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-top-right .wp-block-cover__inner-container{margin:0;width:auto}.wp-block-cover .wp-block-cover__image-background,.wp-block-cover video.wp-block-cover__video-background,.wp-block-cover-image .wp-block-cover__image-background,.wp-block-cover-image video.wp-block-cover__video-background{border:none;bottom:0;box-shadow:none;height:100%;left:0;margin:0;max-height:none;max-width:none;object-fit:cover;outline:none;padding:0;position:absolute;right:0;top:0;width:100%}.wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{background-attachment:fixed;background-repeat:no-repeat;background-size:cover}@supports (-webkit-touch-callout:inherit){.wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{background-attachment:scroll}}@media (prefers-reduced-motion:reduce){.wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{background-attachment:scroll}}.wp-block-cover-image.is-repeated,.wp-block-cover.is-repeated,.wp-block-cover__image-background.is-repeated,video.wp-block-cover__video-background.is-repeated{background-repeat:repeat;background-size:auto}.wp-block-cover-image-text,.wp-block-cover-image-text a,.wp-block-cover-image-text a:active,.wp-block-cover-image-text a:focus,.wp-block-cover-image-text a:hover,.wp-block-cover-text,.wp-block-cover-text a,.wp-block-cover-text a:active,.wp-block-cover-text a:focus,.wp-block-cover-text a:hover,section.wp-block-cover-image h2,section.wp-block-cover-image h2 a,section.wp-block-cover-image h2 a:active,section.wp-block-cover-image h2 a:focus,section.wp-block-cover-image h2 a:hover{color:#fff}.wp-block-cover-image .wp-block-cover.has-left-content{justify-content:flex-start}.wp-block-cover-image .wp-block-cover.has-right-content{justify-content:flex-end}.wp-block-cover-image.has-left-content .wp-block-cover-image-text,.wp-block-cover.has-left-content .wp-block-cover-text,section.wp-block-cover-image.has-left-content>h2{margin-left:0;text-align:left}.wp-block-cover-image.has-right-content .wp-block-cover-image-text,.wp-block-cover.has-right-content .wp-block-cover-text,section.wp-block-cover-image.has-right-content>h2{margin-right:0;text-align:right}.wp-block-cover .wp-block-cover-text,.wp-block-cover-image .wp-block-cover-image-text,section.wp-block-cover-image>h2{font-size:2em;line-height:1.25;margin-bottom:0;max-width:840px;padding:.44em;text-align:center;z-index:1}:where(.wp-block-cover-image:not(.has-text-color)),:where(.wp-block-cover:not(.has-text-color)){color:#fff}:where(.wp-block-cover-image.is-light:not(.has-text-color)),:where(.wp-block-cover.is-light:not(.has-text-color)){color:#000}:root :where(.wp-block-cover h1:not(.has-text-color)),:root :where(.wp-block-cover h2:not(.has-text-color)),:root :where(.wp-block-cover h3:not(.has-text-color)),:root :where(.wp-block-cover h4:not(.has-text-color)),:root :where(.wp-block-cover h5:not(.has-text-color)),:root :where(.wp-block-cover h6:not(.has-text-color)),:root :where(.wp-block-cover p:not(.has-text-color)){color:inherit}body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__image-background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__video-background{z-index:0}body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__gradient-background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__inner-container,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)).has-background-dim:not(.has-background-gradient):before{z-index:1}.has-modal-open body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__inner-container{z-index:auto}PKt\O LLcover/style-rtl.cssnu[.wp-block-cover,.wp-block-cover-image{ align-items:center; background-position:50%; box-sizing:border-box; direction:ltr; display:flex; justify-content:center; min-height:430px; overflow:hidden; overflow:clip; padding:1em; position:relative; } .wp-block-cover .has-background-dim:not([class*=-background-color]),.wp-block-cover-image .has-background-dim:not([class*=-background-color]),.wp-block-cover-image.has-background-dim:not([class*=-background-color]),.wp-block-cover.has-background-dim:not([class*=-background-color]){ background-color:#000; } .wp-block-cover .has-background-dim.has-background-gradient,.wp-block-cover-image .has-background-dim.has-background-gradient{ background-color:initial; } .wp-block-cover-image.has-background-dim:before,.wp-block-cover.has-background-dim:before{ background-color:inherit; content:""; } .wp-block-cover .wp-block-cover__background,.wp-block-cover .wp-block-cover__gradient-background,.wp-block-cover-image .wp-block-cover__background,.wp-block-cover-image .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim:not(.has-background-gradient):before,.wp-block-cover.has-background-dim:not(.has-background-gradient):before{ bottom:0; left:0; opacity:.5; position:absolute; right:0; top:0; } .wp-block-cover-image.has-background-dim.has-background-dim-10 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-10 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-10:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-10 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-10 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-10:not(.has-background-gradient):before{ opacity:.1; } .wp-block-cover-image.has-background-dim.has-background-dim-20 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-20 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-20:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-20 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-20 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-20:not(.has-background-gradient):before{ opacity:.2; } .wp-block-cover-image.has-background-dim.has-background-dim-30 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-30 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-30:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-30 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-30 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-30:not(.has-background-gradient):before{ opacity:.3; } .wp-block-cover-image.has-background-dim.has-background-dim-40 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-40 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-40:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-40 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-40 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-40:not(.has-background-gradient):before{ opacity:.4; } .wp-block-cover-image.has-background-dim.has-background-dim-50 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-50 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-50:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-50 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-50 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-50:not(.has-background-gradient):before{ opacity:.5; } .wp-block-cover-image.has-background-dim.has-background-dim-60 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-60 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-60:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-60 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-60 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-60:not(.has-background-gradient):before{ opacity:.6; } .wp-block-cover-image.has-background-dim.has-background-dim-70 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-70 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-70:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-70 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-70 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-70:not(.has-background-gradient):before{ opacity:.7; } .wp-block-cover-image.has-background-dim.has-background-dim-80 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-80 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-80:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-80 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-80 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-80:not(.has-background-gradient):before{ opacity:.8; } .wp-block-cover-image.has-background-dim.has-background-dim-90 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-90 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-90:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-90 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-90 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-90:not(.has-background-gradient):before{ opacity:.9; } .wp-block-cover-image.has-background-dim.has-background-dim-100 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-100 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-100:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-100 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-100 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-100:not(.has-background-gradient):before{ opacity:1; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-0,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-0,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-0,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-0{ opacity:0; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-10,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-10,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-10,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-10{ opacity:.1; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-20,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-20,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-20,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-20{ opacity:.2; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-30,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-30,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-30,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-30{ opacity:.3; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-40,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-40,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-40,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-40{ opacity:.4; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-50,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-50,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-50,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-50{ opacity:.5; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-60,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-60,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-60,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-60{ opacity:.6; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-70,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-70,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-70,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-70{ opacity:.7; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-80,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-80,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-80,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-80{ opacity:.8; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-90,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-90,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-90,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-90{ opacity:.9; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-100,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-100,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-100,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-100{ opacity:1; } .wp-block-cover-image.alignleft,.wp-block-cover-image.alignright,.wp-block-cover.alignleft,.wp-block-cover.alignright{ max-width:420px; width:100%; } .wp-block-cover-image.aligncenter,.wp-block-cover-image.alignleft,.wp-block-cover-image.alignright,.wp-block-cover.aligncenter,.wp-block-cover.alignleft,.wp-block-cover.alignright{ display:flex; } .wp-block-cover .wp-block-cover__inner-container,.wp-block-cover-image .wp-block-cover__inner-container{ color:inherit; direction:rtl; position:relative; width:100%; } .wp-block-cover-image.is-position-top-left,.wp-block-cover.is-position-top-left{ align-items:flex-start; justify-content:flex-start; } .wp-block-cover-image.is-position-top-center,.wp-block-cover.is-position-top-center{ align-items:flex-start; justify-content:center; } .wp-block-cover-image.is-position-top-right,.wp-block-cover.is-position-top-right{ align-items:flex-start; justify-content:flex-end; } .wp-block-cover-image.is-position-center-left,.wp-block-cover.is-position-center-left{ align-items:center; justify-content:flex-start; } .wp-block-cover-image.is-position-center-center,.wp-block-cover.is-position-center-center{ align-items:center; justify-content:center; } .wp-block-cover-image.is-position-center-right,.wp-block-cover.is-position-center-right{ align-items:center; justify-content:flex-end; } .wp-block-cover-image.is-position-bottom-left,.wp-block-cover.is-position-bottom-left{ align-items:flex-end; justify-content:flex-start; } .wp-block-cover-image.is-position-bottom-center,.wp-block-cover.is-position-bottom-center{ align-items:flex-end; justify-content:center; } .wp-block-cover-image.is-position-bottom-right,.wp-block-cover.is-position-bottom-right{ align-items:flex-end; justify-content:flex-end; } .wp-block-cover-image.has-custom-content-position.has-custom-content-position .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position .wp-block-cover__inner-container{ margin:0; } .wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-bottom-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-bottom-right .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-center-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-center-right .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-top-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-top-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-bottom-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-bottom-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-center-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-center-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-top-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-top-right .wp-block-cover__inner-container{ margin:0; width:auto; } .wp-block-cover .wp-block-cover__image-background,.wp-block-cover video.wp-block-cover__video-background,.wp-block-cover-image .wp-block-cover__image-background,.wp-block-cover-image video.wp-block-cover__video-background{ border:none; bottom:0; box-shadow:none; height:100%; left:0; margin:0; max-height:none; max-width:none; object-fit:cover; outline:none; padding:0; position:absolute; right:0; top:0; width:100%; } .wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{ background-attachment:fixed; background-repeat:no-repeat; background-size:cover; } @supports (-webkit-touch-callout:inherit){ .wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{ background-attachment:scroll; } } @media (prefers-reduced-motion:reduce){ .wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{ background-attachment:scroll; } } .wp-block-cover-image.is-repeated,.wp-block-cover.is-repeated,.wp-block-cover__image-background.is-repeated,video.wp-block-cover__video-background.is-repeated{ background-repeat:repeat; background-size:auto; } .wp-block-cover-image-text,.wp-block-cover-image-text a,.wp-block-cover-image-text a:active,.wp-block-cover-image-text a:focus,.wp-block-cover-image-text a:hover,.wp-block-cover-text,.wp-block-cover-text a,.wp-block-cover-text a:active,.wp-block-cover-text a:focus,.wp-block-cover-text a:hover,section.wp-block-cover-image h2,section.wp-block-cover-image h2 a,section.wp-block-cover-image h2 a:active,section.wp-block-cover-image h2 a:focus,section.wp-block-cover-image h2 a:hover{ color:#fff; } .wp-block-cover-image .wp-block-cover.has-left-content{ justify-content:flex-start; } .wp-block-cover-image .wp-block-cover.has-right-content{ justify-content:flex-end; } .wp-block-cover-image.has-left-content .wp-block-cover-image-text,.wp-block-cover.has-left-content .wp-block-cover-text,section.wp-block-cover-image.has-left-content>h2{ margin-right:0; text-align:right; } .wp-block-cover-image.has-right-content .wp-block-cover-image-text,.wp-block-cover.has-right-content .wp-block-cover-text,section.wp-block-cover-image.has-right-content>h2{ margin-left:0; text-align:left; } .wp-block-cover .wp-block-cover-text,.wp-block-cover-image .wp-block-cover-image-text,section.wp-block-cover-image>h2{ font-size:2em; line-height:1.25; margin-bottom:0; max-width:840px; padding:.44em; text-align:center; z-index:1; } :where(.wp-block-cover-image:not(.has-text-color)),:where(.wp-block-cover:not(.has-text-color)){ color:#fff; } :where(.wp-block-cover-image.is-light:not(.has-text-color)),:where(.wp-block-cover.is-light:not(.has-text-color)){ color:#000; } :root :where(.wp-block-cover h1:not(.has-text-color)),:root :where(.wp-block-cover h2:not(.has-text-color)),:root :where(.wp-block-cover h3:not(.has-text-color)),:root :where(.wp-block-cover h4:not(.has-text-color)),:root :where(.wp-block-cover h5:not(.has-text-color)),:root :where(.wp-block-cover h6:not(.has-text-color)),:root :where(.wp-block-cover p:not(.has-text-color)){ color:inherit; } body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__image-background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__video-background{ z-index:0; } body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__gradient-background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__inner-container,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)).has-background-dim:not(.has-background-gradient):before{ z-index:1; } .has-modal-open body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__inner-container{ z-index:auto; }PKt\Zcover/editor-rtl.cssnu[.wp-block-cover.is-placeholder{ align-items:stretch; display:flex; min-height:240px; padding:0 !important; } .wp-block-cover.is-placeholder .components-placeholder.is-large{ justify-content:flex-start; z-index:1; } .wp-block-cover.is-placeholder:focus:after{ min-height:auto; } .wp-block-cover.components-placeholder h2{ color:inherit; } .wp-block-cover.is-transient{ position:relative; } .wp-block-cover.is-transient:before{ background-color:#fff; content:""; height:100%; opacity:.3; position:absolute; width:100%; z-index:1; } .wp-block-cover.is-transient .wp-block-cover__inner-container{ z-index:2; } .wp-block-cover .components-spinner{ margin:0; position:absolute; right:50%; top:50%; transform:translate(50%, -50%); } .wp-block-cover .wp-block-cover__inner-container{ margin-left:0; margin-right:0; text-align:right; } .wp-block-cover .wp-block-cover__placeholder-background-options{ width:100%; } .wp-block-cover .wp-block-cover__image--placeholder-image{ bottom:0; left:0; position:absolute; right:0; top:0; } [data-align=left]>.wp-block-cover,[data-align=right]>.wp-block-cover{ max-width:420px; width:100%; } .block-library-cover__reset-button{ margin-right:auto; } .block-library-cover__resize-container{ bottom:0; left:0; min-height:50px; position:absolute !important; right:0; top:0; } .components-popover.block-editor-block-popover.block-library-cover__resizable-box-popover .block-library-cover__resize-container,.components-popover.block-editor-block-popover.block-library-cover__resizable-box-popover .components-popover__content>div{ overflow:visible; pointer-events:none; } .block-editor-block-patterns-list__list-item .has-parallax.wp-block-cover{ background-attachment:scroll; } .color-block-support-panel__inner-wrapper>:not(.block-editor-tools-panel-color-gradient-settings__item){ margin-top:24px; }PKt\\>LLLcover/style.cssnu[.wp-block-cover,.wp-block-cover-image{ align-items:center; background-position:50%; box-sizing:border-box; display:flex; justify-content:center; min-height:430px; overflow:hidden; overflow:clip; padding:1em; position:relative; } .wp-block-cover .has-background-dim:not([class*=-background-color]),.wp-block-cover-image .has-background-dim:not([class*=-background-color]),.wp-block-cover-image.has-background-dim:not([class*=-background-color]),.wp-block-cover.has-background-dim:not([class*=-background-color]){ background-color:#000; } .wp-block-cover .has-background-dim.has-background-gradient,.wp-block-cover-image .has-background-dim.has-background-gradient{ background-color:initial; } .wp-block-cover-image.has-background-dim:before,.wp-block-cover.has-background-dim:before{ background-color:inherit; content:""; } .wp-block-cover .wp-block-cover__background,.wp-block-cover .wp-block-cover__gradient-background,.wp-block-cover-image .wp-block-cover__background,.wp-block-cover-image .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim:not(.has-background-gradient):before,.wp-block-cover.has-background-dim:not(.has-background-gradient):before{ bottom:0; left:0; opacity:.5; position:absolute; right:0; top:0; } .wp-block-cover-image.has-background-dim.has-background-dim-10 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-10 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-10:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-10 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-10 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-10:not(.has-background-gradient):before{ opacity:.1; } .wp-block-cover-image.has-background-dim.has-background-dim-20 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-20 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-20:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-20 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-20 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-20:not(.has-background-gradient):before{ opacity:.2; } .wp-block-cover-image.has-background-dim.has-background-dim-30 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-30 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-30:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-30 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-30 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-30:not(.has-background-gradient):before{ opacity:.3; } .wp-block-cover-image.has-background-dim.has-background-dim-40 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-40 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-40:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-40 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-40 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-40:not(.has-background-gradient):before{ opacity:.4; } .wp-block-cover-image.has-background-dim.has-background-dim-50 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-50 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-50:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-50 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-50 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-50:not(.has-background-gradient):before{ opacity:.5; } .wp-block-cover-image.has-background-dim.has-background-dim-60 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-60 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-60:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-60 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-60 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-60:not(.has-background-gradient):before{ opacity:.6; } .wp-block-cover-image.has-background-dim.has-background-dim-70 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-70 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-70:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-70 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-70 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-70:not(.has-background-gradient):before{ opacity:.7; } .wp-block-cover-image.has-background-dim.has-background-dim-80 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-80 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-80:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-80 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-80 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-80:not(.has-background-gradient):before{ opacity:.8; } .wp-block-cover-image.has-background-dim.has-background-dim-90 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-90 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-90:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-90 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-90 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-90:not(.has-background-gradient):before{ opacity:.9; } .wp-block-cover-image.has-background-dim.has-background-dim-100 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-100 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-100:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-100 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-100 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-100:not(.has-background-gradient):before{ opacity:1; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-0,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-0,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-0,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-0{ opacity:0; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-10,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-10,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-10,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-10{ opacity:.1; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-20,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-20,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-20,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-20{ opacity:.2; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-30,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-30,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-30,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-30{ opacity:.3; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-40,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-40,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-40,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-40{ opacity:.4; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-50,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-50,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-50,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-50{ opacity:.5; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-60,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-60,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-60,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-60{ opacity:.6; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-70,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-70,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-70,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-70{ opacity:.7; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-80,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-80,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-80,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-80{ opacity:.8; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-90,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-90,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-90,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-90{ opacity:.9; } .wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-100,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-100,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-100,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-100{ opacity:1; } .wp-block-cover-image.alignleft,.wp-block-cover-image.alignright,.wp-block-cover.alignleft,.wp-block-cover.alignright{ max-width:420px; width:100%; } .wp-block-cover-image.aligncenter,.wp-block-cover-image.alignleft,.wp-block-cover-image.alignright,.wp-block-cover.aligncenter,.wp-block-cover.alignleft,.wp-block-cover.alignright{ display:flex; } .wp-block-cover .wp-block-cover__inner-container,.wp-block-cover-image .wp-block-cover__inner-container{ color:inherit; position:relative; width:100%; } .wp-block-cover-image.is-position-top-left,.wp-block-cover.is-position-top-left{ align-items:flex-start; justify-content:flex-start; } .wp-block-cover-image.is-position-top-center,.wp-block-cover.is-position-top-center{ align-items:flex-start; justify-content:center; } .wp-block-cover-image.is-position-top-right,.wp-block-cover.is-position-top-right{ align-items:flex-start; justify-content:flex-end; } .wp-block-cover-image.is-position-center-left,.wp-block-cover.is-position-center-left{ align-items:center; justify-content:flex-start; } .wp-block-cover-image.is-position-center-center,.wp-block-cover.is-position-center-center{ align-items:center; justify-content:center; } .wp-block-cover-image.is-position-center-right,.wp-block-cover.is-position-center-right{ align-items:center; justify-content:flex-end; } .wp-block-cover-image.is-position-bottom-left,.wp-block-cover.is-position-bottom-left{ align-items:flex-end; justify-content:flex-start; } .wp-block-cover-image.is-position-bottom-center,.wp-block-cover.is-position-bottom-center{ align-items:flex-end; justify-content:center; } .wp-block-cover-image.is-position-bottom-right,.wp-block-cover.is-position-bottom-right{ align-items:flex-end; justify-content:flex-end; } .wp-block-cover-image.has-custom-content-position.has-custom-content-position .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position .wp-block-cover__inner-container{ margin:0; } .wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-bottom-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-bottom-right .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-center-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-center-right .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-top-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-top-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-bottom-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-bottom-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-center-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-center-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-top-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-top-right .wp-block-cover__inner-container{ margin:0; width:auto; } .wp-block-cover .wp-block-cover__image-background,.wp-block-cover video.wp-block-cover__video-background,.wp-block-cover-image .wp-block-cover__image-background,.wp-block-cover-image video.wp-block-cover__video-background{ border:none; bottom:0; box-shadow:none; height:100%; left:0; margin:0; max-height:none; max-width:none; object-fit:cover; outline:none; padding:0; position:absolute; right:0; top:0; width:100%; } .wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{ background-attachment:fixed; background-repeat:no-repeat; background-size:cover; } @supports (-webkit-touch-callout:inherit){ .wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{ background-attachment:scroll; } } @media (prefers-reduced-motion:reduce){ .wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{ background-attachment:scroll; } } .wp-block-cover-image.is-repeated,.wp-block-cover.is-repeated,.wp-block-cover__image-background.is-repeated,video.wp-block-cover__video-background.is-repeated{ background-repeat:repeat; background-size:auto; } .wp-block-cover-image-text,.wp-block-cover-image-text a,.wp-block-cover-image-text a:active,.wp-block-cover-image-text a:focus,.wp-block-cover-image-text a:hover,.wp-block-cover-text,.wp-block-cover-text a,.wp-block-cover-text a:active,.wp-block-cover-text a:focus,.wp-block-cover-text a:hover,section.wp-block-cover-image h2,section.wp-block-cover-image h2 a,section.wp-block-cover-image h2 a:active,section.wp-block-cover-image h2 a:focus,section.wp-block-cover-image h2 a:hover{ color:#fff; } .wp-block-cover-image .wp-block-cover.has-left-content{ justify-content:flex-start; } .wp-block-cover-image .wp-block-cover.has-right-content{ justify-content:flex-end; } .wp-block-cover-image.has-left-content .wp-block-cover-image-text,.wp-block-cover.has-left-content .wp-block-cover-text,section.wp-block-cover-image.has-left-content>h2{ margin-left:0; text-align:left; } .wp-block-cover-image.has-right-content .wp-block-cover-image-text,.wp-block-cover.has-right-content .wp-block-cover-text,section.wp-block-cover-image.has-right-content>h2{ margin-right:0; text-align:right; } .wp-block-cover .wp-block-cover-text,.wp-block-cover-image .wp-block-cover-image-text,section.wp-block-cover-image>h2{ font-size:2em; line-height:1.25; margin-bottom:0; max-width:840px; padding:.44em; text-align:center; z-index:1; } :where(.wp-block-cover-image:not(.has-text-color)),:where(.wp-block-cover:not(.has-text-color)){ color:#fff; } :where(.wp-block-cover-image.is-light:not(.has-text-color)),:where(.wp-block-cover.is-light:not(.has-text-color)){ color:#000; } :root :where(.wp-block-cover h1:not(.has-text-color)),:root :where(.wp-block-cover h2:not(.has-text-color)),:root :where(.wp-block-cover h3:not(.has-text-color)),:root :where(.wp-block-cover h4:not(.has-text-color)),:root :where(.wp-block-cover h5:not(.has-text-color)),:root :where(.wp-block-cover h6:not(.has-text-color)),:root :where(.wp-block-cover p:not(.has-text-color)){ color:inherit; } body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__image-background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__video-background{ z-index:0; } body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__gradient-background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__inner-container,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)).has-background-dim:not(.has-background-gradient):before{ z-index:1; } .has-modal-open body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__inner-container{ z-index:auto; }PKt\`}ZJJcover/style-rtl.min.cssnu[.wp-block-cover,.wp-block-cover-image{align-items:center;background-position:50%;box-sizing:border-box;direction:ltr;display:flex;justify-content:center;min-height:430px;overflow:hidden;overflow:clip;padding:1em;position:relative}.wp-block-cover .has-background-dim:not([class*=-background-color]),.wp-block-cover-image .has-background-dim:not([class*=-background-color]),.wp-block-cover-image.has-background-dim:not([class*=-background-color]),.wp-block-cover.has-background-dim:not([class*=-background-color]){background-color:#000}.wp-block-cover .has-background-dim.has-background-gradient,.wp-block-cover-image .has-background-dim.has-background-gradient{background-color:initial}.wp-block-cover-image.has-background-dim:before,.wp-block-cover.has-background-dim:before{background-color:inherit;content:""}.wp-block-cover .wp-block-cover__background,.wp-block-cover .wp-block-cover__gradient-background,.wp-block-cover-image .wp-block-cover__background,.wp-block-cover-image .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim:not(.has-background-gradient):before,.wp-block-cover.has-background-dim:not(.has-background-gradient):before{bottom:0;left:0;opacity:.5;position:absolute;right:0;top:0}.wp-block-cover-image.has-background-dim.has-background-dim-10 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-10 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-10:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-10 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-10 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-10:not(.has-background-gradient):before{opacity:.1}.wp-block-cover-image.has-background-dim.has-background-dim-20 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-20 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-20:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-20 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-20 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-20:not(.has-background-gradient):before{opacity:.2}.wp-block-cover-image.has-background-dim.has-background-dim-30 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-30 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-30:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-30 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-30 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-30:not(.has-background-gradient):before{opacity:.3}.wp-block-cover-image.has-background-dim.has-background-dim-40 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-40 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-40:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-40 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-40 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-40:not(.has-background-gradient):before{opacity:.4}.wp-block-cover-image.has-background-dim.has-background-dim-50 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-50 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-50:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-50 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-50 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-50:not(.has-background-gradient):before{opacity:.5}.wp-block-cover-image.has-background-dim.has-background-dim-60 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-60 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-60:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-60 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-60 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-60:not(.has-background-gradient):before{opacity:.6}.wp-block-cover-image.has-background-dim.has-background-dim-70 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-70 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-70:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-70 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-70 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-70:not(.has-background-gradient):before{opacity:.7}.wp-block-cover-image.has-background-dim.has-background-dim-80 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-80 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-80:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-80 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-80 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-80:not(.has-background-gradient):before{opacity:.8}.wp-block-cover-image.has-background-dim.has-background-dim-90 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-90 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-90:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-90 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-90 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-90:not(.has-background-gradient):before{opacity:.9}.wp-block-cover-image.has-background-dim.has-background-dim-100 .wp-block-cover__background,.wp-block-cover-image.has-background-dim.has-background-dim-100 .wp-block-cover__gradient-background,.wp-block-cover-image.has-background-dim.has-background-dim-100:not(.has-background-gradient):before,.wp-block-cover.has-background-dim.has-background-dim-100 .wp-block-cover__background,.wp-block-cover.has-background-dim.has-background-dim-100 .wp-block-cover__gradient-background,.wp-block-cover.has-background-dim.has-background-dim-100:not(.has-background-gradient):before{opacity:1}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-0,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-0,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-0,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-0{opacity:0}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-10,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-10,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-10,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-10{opacity:.1}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-20,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-20,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-20,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-20{opacity:.2}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-30,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-30,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-30,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-30{opacity:.3}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-40,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-40,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-40,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-40{opacity:.4}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-50,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-50,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-50,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-50{opacity:.5}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-60,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-60,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-60,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-60{opacity:.6}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-70,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-70,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-70,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-70{opacity:.7}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-80,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-80,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-80,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-80{opacity:.8}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-90,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-90,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-90,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-90{opacity:.9}.wp-block-cover .wp-block-cover__background.has-background-dim.has-background-dim-100,.wp-block-cover .wp-block-cover__gradient-background.has-background-dim.has-background-dim-100,.wp-block-cover-image .wp-block-cover__background.has-background-dim.has-background-dim-100,.wp-block-cover-image .wp-block-cover__gradient-background.has-background-dim.has-background-dim-100{opacity:1}.wp-block-cover-image.alignleft,.wp-block-cover-image.alignright,.wp-block-cover.alignleft,.wp-block-cover.alignright{max-width:420px;width:100%}.wp-block-cover-image.aligncenter,.wp-block-cover-image.alignleft,.wp-block-cover-image.alignright,.wp-block-cover.aligncenter,.wp-block-cover.alignleft,.wp-block-cover.alignright{display:flex}.wp-block-cover .wp-block-cover__inner-container,.wp-block-cover-image .wp-block-cover__inner-container{color:inherit;direction:rtl;position:relative;width:100%}.wp-block-cover-image.is-position-top-left,.wp-block-cover.is-position-top-left{align-items:flex-start;justify-content:flex-start}.wp-block-cover-image.is-position-top-center,.wp-block-cover.is-position-top-center{align-items:flex-start;justify-content:center}.wp-block-cover-image.is-position-top-right,.wp-block-cover.is-position-top-right{align-items:flex-start;justify-content:flex-end}.wp-block-cover-image.is-position-center-left,.wp-block-cover.is-position-center-left{align-items:center;justify-content:flex-start}.wp-block-cover-image.is-position-center-center,.wp-block-cover.is-position-center-center{align-items:center;justify-content:center}.wp-block-cover-image.is-position-center-right,.wp-block-cover.is-position-center-right{align-items:center;justify-content:flex-end}.wp-block-cover-image.is-position-bottom-left,.wp-block-cover.is-position-bottom-left{align-items:flex-end;justify-content:flex-start}.wp-block-cover-image.is-position-bottom-center,.wp-block-cover.is-position-bottom-center{align-items:flex-end;justify-content:center}.wp-block-cover-image.is-position-bottom-right,.wp-block-cover.is-position-bottom-right{align-items:flex-end;justify-content:flex-end}.wp-block-cover-image.has-custom-content-position.has-custom-content-position .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position .wp-block-cover__inner-container{margin:0}.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-bottom-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-bottom-right .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-center-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-center-right .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-top-left .wp-block-cover__inner-container,.wp-block-cover-image.has-custom-content-position.has-custom-content-position.is-position-top-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-bottom-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-bottom-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-center-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-center-right .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-top-left .wp-block-cover__inner-container,.wp-block-cover.has-custom-content-position.has-custom-content-position.is-position-top-right .wp-block-cover__inner-container{margin:0;width:auto}.wp-block-cover .wp-block-cover__image-background,.wp-block-cover video.wp-block-cover__video-background,.wp-block-cover-image .wp-block-cover__image-background,.wp-block-cover-image video.wp-block-cover__video-background{border:none;bottom:0;box-shadow:none;height:100%;left:0;margin:0;max-height:none;max-width:none;object-fit:cover;outline:none;padding:0;position:absolute;right:0;top:0;width:100%}.wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{background-attachment:fixed;background-repeat:no-repeat;background-size:cover}@supports (-webkit-touch-callout:inherit){.wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{background-attachment:scroll}}@media (prefers-reduced-motion:reduce){.wp-block-cover-image.has-parallax,.wp-block-cover.has-parallax,.wp-block-cover__image-background.has-parallax,video.wp-block-cover__video-background.has-parallax{background-attachment:scroll}}.wp-block-cover-image.is-repeated,.wp-block-cover.is-repeated,.wp-block-cover__image-background.is-repeated,video.wp-block-cover__video-background.is-repeated{background-repeat:repeat;background-size:auto}.wp-block-cover-image-text,.wp-block-cover-image-text a,.wp-block-cover-image-text a:active,.wp-block-cover-image-text a:focus,.wp-block-cover-image-text a:hover,.wp-block-cover-text,.wp-block-cover-text a,.wp-block-cover-text a:active,.wp-block-cover-text a:focus,.wp-block-cover-text a:hover,section.wp-block-cover-image h2,section.wp-block-cover-image h2 a,section.wp-block-cover-image h2 a:active,section.wp-block-cover-image h2 a:focus,section.wp-block-cover-image h2 a:hover{color:#fff}.wp-block-cover-image .wp-block-cover.has-left-content{justify-content:flex-start}.wp-block-cover-image .wp-block-cover.has-right-content{justify-content:flex-end}.wp-block-cover-image.has-left-content .wp-block-cover-image-text,.wp-block-cover.has-left-content .wp-block-cover-text,section.wp-block-cover-image.has-left-content>h2{margin-right:0;text-align:right}.wp-block-cover-image.has-right-content .wp-block-cover-image-text,.wp-block-cover.has-right-content .wp-block-cover-text,section.wp-block-cover-image.has-right-content>h2{margin-left:0;text-align:left}.wp-block-cover .wp-block-cover-text,.wp-block-cover-image .wp-block-cover-image-text,section.wp-block-cover-image>h2{font-size:2em;line-height:1.25;margin-bottom:0;max-width:840px;padding:.44em;text-align:center;z-index:1}:where(.wp-block-cover-image:not(.has-text-color)),:where(.wp-block-cover:not(.has-text-color)){color:#fff}:where(.wp-block-cover-image.is-light:not(.has-text-color)),:where(.wp-block-cover.is-light:not(.has-text-color)){color:#000}:root :where(.wp-block-cover h1:not(.has-text-color)),:root :where(.wp-block-cover h2:not(.has-text-color)),:root :where(.wp-block-cover h3:not(.has-text-color)),:root :where(.wp-block-cover h4:not(.has-text-color)),:root :where(.wp-block-cover h5:not(.has-text-color)),:root :where(.wp-block-cover h6:not(.has-text-color)),:root :where(.wp-block-cover p:not(.has-text-color)){color:inherit}body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__image-background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__video-background{z-index:0}body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__gradient-background,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__inner-container,body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)).has-background-dim:not(.has-background-gradient):before{z-index:1}.has-modal-open body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(.wp-block-cover__background+.wp-block-cover__inner-container)) .wp-block-cover__inner-container{z-index:auto}PKt\liqcover/editor-rtl.min.cssnu[.wp-block-cover.is-placeholder{align-items:stretch;display:flex;min-height:240px;padding:0!important}.wp-block-cover.is-placeholder .components-placeholder.is-large{justify-content:flex-start;z-index:1}.wp-block-cover.is-placeholder:focus:after{min-height:auto}.wp-block-cover.components-placeholder h2{color:inherit}.wp-block-cover.is-transient{position:relative}.wp-block-cover.is-transient:before{background-color:#fff;content:"";height:100%;opacity:.3;position:absolute;width:100%;z-index:1}.wp-block-cover.is-transient .wp-block-cover__inner-container{z-index:2}.wp-block-cover .components-spinner{margin:0;position:absolute;right:50%;top:50%;transform:translate(50%,-50%)}.wp-block-cover .wp-block-cover__inner-container{margin-left:0;margin-right:0;text-align:right}.wp-block-cover .wp-block-cover__placeholder-background-options{width:100%}.wp-block-cover .wp-block-cover__image--placeholder-image{bottom:0;left:0;position:absolute;right:0;top:0}[data-align=left]>.wp-block-cover,[data-align=right]>.wp-block-cover{max-width:420px;width:100%}.block-library-cover__reset-button{margin-right:auto}.block-library-cover__resize-container{bottom:0;left:0;min-height:50px;position:absolute!important;right:0;top:0}.components-popover.block-editor-block-popover.block-library-cover__resizable-box-popover .block-library-cover__resize-container,.components-popover.block-editor-block-popover.block-library-cover__resizable-box-popover .components-popover__content>div{overflow:visible;pointer-events:none}.block-editor-block-patterns-list__list-item .has-parallax.wp-block-cover{background-attachment:scroll}.color-block-support-panel__inner-wrapper>:not(.block-editor-tools-panel-color-gradient-settings__item){margin-top:24px}PKt\#}}cover/editor.cssnu[.wp-block-cover.is-placeholder{ align-items:stretch; display:flex; min-height:240px; padding:0 !important; } .wp-block-cover.is-placeholder .components-placeholder.is-large{ justify-content:flex-start; z-index:1; } .wp-block-cover.is-placeholder:focus:after{ min-height:auto; } .wp-block-cover.components-placeholder h2{ color:inherit; } .wp-block-cover.is-transient{ position:relative; } .wp-block-cover.is-transient:before{ background-color:#fff; content:""; height:100%; opacity:.3; position:absolute; width:100%; z-index:1; } .wp-block-cover.is-transient .wp-block-cover__inner-container{ z-index:2; } .wp-block-cover .components-spinner{ left:50%; margin:0; position:absolute; top:50%; transform:translate(-50%, -50%); } .wp-block-cover .wp-block-cover__inner-container{ margin-left:0; margin-right:0; text-align:left; } .wp-block-cover .wp-block-cover__placeholder-background-options{ width:100%; } .wp-block-cover .wp-block-cover__image--placeholder-image{ bottom:0; left:0; position:absolute; right:0; top:0; } [data-align=left]>.wp-block-cover,[data-align=right]>.wp-block-cover{ max-width:420px; width:100%; } .block-library-cover__reset-button{ margin-left:auto; } .block-library-cover__resize-container{ bottom:0; left:0; min-height:50px; position:absolute !important; right:0; top:0; } .components-popover.block-editor-block-popover.block-library-cover__resizable-box-popover .block-library-cover__resize-container,.components-popover.block-editor-block-popover.block-library-cover__resizable-box-popover .components-popover__content>div{ overflow:visible; pointer-events:none; } .block-editor-block-patterns-list__list-item .has-parallax.wp-block-cover{ background-attachment:scroll; } .color-block-support-panel__inner-wrapper>:not(.block-editor-tools-panel-color-gradient-settings__item){ margin-top:24px; }PKt\"''template-part.phpnu[ 'wp_template_part', 'post_status' => 'publish', 'post_name__in' => array( $attributes['slug'] ), 'tax_query' => array( array( 'taxonomy' => 'wp_theme', 'field' => 'name', 'terms' => $theme, ), ), 'posts_per_page' => 1, 'no_found_rows' => true, 'lazy_load_term_meta' => false, // Do not lazy load term meta, as template parts only have one term. ) ); $template_part_post = $template_part_query->have_posts() ? $template_part_query->next_post() : null; if ( $template_part_post ) { // A published post might already exist if this template part was customized elsewhere // or if it's part of a customized template. $block_template = _build_block_template_result_from_post( $template_part_post ); $content = $block_template->content; if ( isset( $block_template->area ) ) { $area = $block_template->area; } /** * Fires when a block template part is loaded from a template post stored in the database. * * @since 5.9.0 * * @param string $template_part_id The requested template part namespaced to the theme. * @param array $attributes The block attributes. * @param WP_Post $template_part_post The template part post object. * @param string $content The template part content. */ do_action( 'render_block_core_template_part_post', $template_part_id, $attributes, $template_part_post, $content ); } else { $template_part_file_path = ''; // Else, if the template part was provided by the active theme, // render the corresponding file content. if ( 0 === validate_file( $attributes['slug'] ) ) { $block_template = get_block_file_template( $template_part_id, 'wp_template_part' ); if ( isset( $block_template->content ) ) { $content = $block_template->content; } if ( isset( $block_template->area ) ) { $area = $block_template->area; } // Needed for the `render_block_core_template_part_file` and `render_block_core_template_part_none` actions below. $block_template_file = _get_block_template_file( 'wp_template_part', $attributes['slug'] ); if ( $block_template_file ) { $template_part_file_path = $block_template_file['path']; } } if ( '' !== $content && null !== $content ) { /** * Fires when a block template part is loaded from a template part in the theme. * * @since 5.9.0 * * @param string $template_part_id The requested template part namespaced to the theme. * @param array $attributes The block attributes. * @param string $template_part_file_path Absolute path to the template path. * @param string $content The template part content. */ do_action( 'render_block_core_template_part_file', $template_part_id, $attributes, $template_part_file_path, $content ); } else { /** * Fires when a requested block template part does not exist in the database nor in the theme. * * @since 5.9.0 * * @param string $template_part_id The requested template part namespaced to the theme. * @param array $attributes The block attributes. * @param string $template_part_file_path Absolute path to the not found template path. */ do_action( 'render_block_core_template_part_none', $template_part_id, $attributes, $template_part_file_path ); } } } // WP_DEBUG_DISPLAY must only be honored when WP_DEBUG. This precedent // is set in `wp_debug_mode()`. $is_debug = WP_DEBUG && WP_DEBUG_DISPLAY; if ( is_null( $content ) ) { if ( $is_debug && isset( $attributes['slug'] ) ) { return sprintf( /* translators: %s: Template part slug. */ __( 'Template part has been deleted or is unavailable: %s' ), $attributes['slug'] ); } return ''; } if ( isset( $seen_ids[ $template_part_id ] ) ) { return $is_debug ? // translators: Visible only in the front end, this warning takes the place of a faulty block. __( '[block rendering halted]' ) : ''; } // Look up area definition. $area_definition = null; $defined_areas = get_allowed_block_template_part_areas(); foreach ( $defined_areas as $defined_area ) { if ( $defined_area['area'] === $area ) { $area_definition = $defined_area; break; } } // If $area is not allowed, set it back to the uncategorized default. if ( ! $area_definition ) { $area = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; } // Run through the actions that are typically taken on the_content. $content = shortcode_unautop( $content ); $content = do_shortcode( $content ); $seen_ids[ $template_part_id ] = true; $content = do_blocks( $content ); unset( $seen_ids[ $template_part_id ] ); $content = wptexturize( $content ); $content = convert_smilies( $content ); $content = wp_filter_content_tags( $content, "template_part_{$area}" ); // Handle embeds for block template parts. global $wp_embed; $content = $wp_embed->autoembed( $content ); if ( empty( $attributes['tagName'] ) || tag_escape( $attributes['tagName'] ) !== $attributes['tagName'] ) { $area_tag = 'div'; if ( $area_definition && isset( $area_definition['area_tag'] ) ) { $area_tag = $area_definition['area_tag']; } $html_tag = $area_tag; } else { $html_tag = esc_attr( $attributes['tagName'] ); } $wrapper_attributes = get_block_wrapper_attributes(); return "<$html_tag $wrapper_attributes>" . str_replace( ']]>', ']]>', $content ) . ""; } /** * Returns an array of area variation objects for the template part block. * * @since 6.1.0 * * @param array $instance_variations The variations for instances. * * @return array Array containing the block variation objects. */ function build_template_part_block_area_variations( $instance_variations ) { $variations = array(); $defined_areas = get_allowed_block_template_part_areas(); foreach ( $defined_areas as $area ) { if ( 'uncategorized' !== $area['area'] ) { $has_instance_for_area = false; foreach ( $instance_variations as $variation ) { if ( $variation['attributes']['area'] === $area['area'] ) { $has_instance_for_area = true; break; } } $scope = $has_instance_for_area ? array() : array( 'inserter' ); $variations[] = array( 'name' => 'area_' . $area['area'], 'title' => $area['label'], 'description' => $area['description'], 'attributes' => array( 'area' => $area['area'], ), 'scope' => $scope, 'icon' => $area['icon'], ); } } return $variations; } /** * Returns an array of instance variation objects for the template part block * * @since 6.1.0 * * @return array Array containing the block variation objects. */ function build_template_part_block_instance_variations() { // Block themes are unavailable during installation. if ( wp_installing() ) { return array(); } if ( ! current_theme_supports( 'block-templates' ) && ! current_theme_supports( 'block-template-parts' ) ) { return array(); } $variations = array(); $template_parts = get_block_templates( array( 'post_type' => 'wp_template_part', ), 'wp_template_part' ); $defined_areas = get_allowed_block_template_part_areas(); $icon_by_area = array_combine( array_column( $defined_areas, 'area' ), array_column( $defined_areas, 'icon' ) ); foreach ( $template_parts as $template_part ) { $variations[] = array( 'name' => 'instance_' . sanitize_title( $template_part->slug ), 'title' => $template_part->title, // If there's no description for the template part don't show the // block description. This is a bit hacky, but prevent the fallback // by using a non-breaking space so that the value of description // isn't falsey. 'description' => $template_part->description || ' ', 'attributes' => array( 'slug' => $template_part->slug, 'theme' => $template_part->theme, 'area' => $template_part->area, ), 'scope' => array( 'inserter' ), 'icon' => isset( $icon_by_area[ $template_part->area ] ) ? $icon_by_area[ $template_part->area ] : null, 'example' => array( 'attributes' => array( 'slug' => $template_part->slug, 'theme' => $template_part->theme, 'area' => $template_part->area, ), ), ); } return $variations; } /** * Returns an array of all template part block variations. * * @since 5.9.0 * * @return array Array containing the block variation objects. */ function build_template_part_block_variations() { $instance_variations = build_template_part_block_instance_variations(); $area_variations = build_template_part_block_area_variations( $instance_variations ); return array_merge( $area_variations, $instance_variations ); } /** * Registers the `core/template-part` block on the server. * * @since 5.9.0 */ function register_block_core_template_part() { register_block_type_from_metadata( __DIR__ . '/template-part', array( 'render_callback' => 'render_block_core_template_part', 'variation_callback' => 'build_template_part_block_variations', ) ); } add_action( 'init', 'register_block_core_template_part' ); PKt\ܣllpost-content.phpnu[context['postId'] ) ) { return ''; } $post_id = $block->context['postId']; if ( isset( $seen_ids[ $post_id ] ) ) { // WP_DEBUG_DISPLAY must only be honored when WP_DEBUG. This precedent // is set in `wp_debug_mode()`. $is_debug = WP_DEBUG && WP_DEBUG_DISPLAY; return $is_debug ? // translators: Visible only in the front end, this warning takes the place of a faulty block. __( '[block rendering halted]' ) : ''; } $seen_ids[ $post_id ] = true; // When inside the main loop, we want to use queried object // so that `the_preview` for the current post can apply. // We force this behavior by omitting the third argument (post ID) from the `get_the_content`. $content = get_the_content(); // Check for nextpage to display page links for paginated posts. if ( has_block( 'core/nextpage' ) ) { $content .= wp_link_pages( array( 'echo' => 0 ) ); } /** This filter is documented in wp-includes/post-template.php */ $content = apply_filters( 'the_content', str_replace( ']]>', ']]>', $content ) ); unset( $seen_ids[ $post_id ] ); if ( empty( $content ) ) { return ''; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => 'entry-content' ) ); return ( '
          ' . $content . '
          ' ); } /** * Registers the `core/post-content` block on the server. * * @since 5.8.0 */ function register_block_core_post_content() { register_block_type_from_metadata( __DIR__ . '/post-content', array( 'render_callback' => 'render_block_core_post_content', ) ); } add_action( 'init', 'register_block_core_post_content' ); PKt\D post-terms/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/post-terms", "title": "Post Terms", "category": "theme", "description": "Post terms.", "textdomain": "default", "attributes": { "term": { "type": "string" }, "textAlign": { "type": "string" }, "separator": { "type": "string", "default": ", " }, "prefix": { "type": "string", "default": "" }, "suffix": { "type": "string", "default": "" } }, "usesContext": [ "postId", "postType" ], "example": { "viewportWidth": 350 }, "supports": { "html": false, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true, "link": true } }, "spacing": { "margin": true, "padding": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "style": "wp-block-post-terms" } PKt\]E[uupost-terms/style.min.cssnu[.wp-block-post-terms{box-sizing:border-box}.wp-block-post-terms .wp-block-post-terms__separator{white-space:pre-wrap}PKt\.post-terms/style-rtl.cssnu[.wp-block-post-terms{ box-sizing:border-box; } .wp-block-post-terms .wp-block-post-terms__separator{ white-space:pre-wrap; }PKt\.post-terms/style.cssnu[.wp-block-post-terms{ box-sizing:border-box; } .wp-block-post-terms .wp-block-post-terms__separator{ white-space:pre-wrap; }PKt\]E[uupost-terms/style-rtl.min.cssnu[.wp-block-post-terms{box-sizing:border-box}.wp-block-post-terms .wp-block-post-terms__separator{white-space:pre-wrap}PKt\2post-excerpt/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/post-excerpt", "title": "Excerpt", "category": "theme", "description": "Display the excerpt.", "textdomain": "default", "attributes": { "textAlign": { "type": "string" }, "moreText": { "type": "string" }, "showMoreOnNewLine": { "type": "boolean", "default": true }, "excerptLength": { "type": "number", "default": 55 } }, "usesContext": [ "postId", "postType", "queryId" ], "example": { "viewportWidth": 350 }, "supports": { "html": false, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true, "link": true } }, "spacing": { "margin": true, "padding": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "editorStyle": "wp-block-post-excerpt-editor", "style": "wp-block-post-excerpt" } PKt\*}PPpost-excerpt/editor.min.cssnu[.wp-block-post-excerpt .wp-block-post-excerpt__excerpt.is-inline{display:inline}PKt\3SSpost-excerpt/style.min.cssnu[:where(.wp-block-post-excerpt){box-sizing:border-box;margin-bottom:var(--wp--style--block-gap);margin-top:var(--wp--style--block-gap)}.wp-block-post-excerpt__excerpt{margin-bottom:0;margin-top:0}.wp-block-post-excerpt__more-text{margin-bottom:0;margin-top:var(--wp--style--block-gap)}.wp-block-post-excerpt__more-link{display:inline-block}PKt\+Vyypost-excerpt/style-rtl.cssnu[:where(.wp-block-post-excerpt){ box-sizing:border-box; margin-bottom:var(--wp--style--block-gap); margin-top:var(--wp--style--block-gap); } .wp-block-post-excerpt__excerpt{ margin-bottom:0; margin-top:0; } .wp-block-post-excerpt__more-text{ margin-bottom:0; margin-top:var(--wp--style--block-gap); } .wp-block-post-excerpt__more-link{ display:inline-block; }PKt\ٵUUpost-excerpt/editor-rtl.cssnu[.wp-block-post-excerpt .wp-block-post-excerpt__excerpt.is-inline{ display:inline; }PKt\+Vyypost-excerpt/style.cssnu[:where(.wp-block-post-excerpt){ box-sizing:border-box; margin-bottom:var(--wp--style--block-gap); margin-top:var(--wp--style--block-gap); } .wp-block-post-excerpt__excerpt{ margin-bottom:0; margin-top:0; } .wp-block-post-excerpt__more-text{ margin-bottom:0; margin-top:var(--wp--style--block-gap); } .wp-block-post-excerpt__more-link{ display:inline-block; }PKt\3SSpost-excerpt/style-rtl.min.cssnu[:where(.wp-block-post-excerpt){box-sizing:border-box;margin-bottom:var(--wp--style--block-gap);margin-top:var(--wp--style--block-gap)}.wp-block-post-excerpt__excerpt{margin-bottom:0;margin-top:0}.wp-block-post-excerpt__more-text{margin-bottom:0;margin-top:var(--wp--style--block-gap)}.wp-block-post-excerpt__more-link{display:inline-block}PKt\*}PPpost-excerpt/editor-rtl.min.cssnu[.wp-block-post-excerpt .wp-block-post-excerpt__excerpt.is-inline{display:inline}PKt\ٵUUpost-excerpt/editor.cssnu[.wp-block-post-excerpt .wp-block-post-excerpt__excerpt.is-inline{ display:inline; }PKt\@ n shortcode.phpnu[ 'render_block_core_shortcode', ) ); } add_action( 'init', 'register_block_core_shortcode' ); PKt\Br88table/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/table", "title": "Table", "category": "text", "description": "Create structured content in rows and columns to display information.", "textdomain": "default", "attributes": { "hasFixedLayout": { "type": "boolean", "default": true }, "caption": { "type": "rich-text", "source": "rich-text", "selector": "figcaption" }, "head": { "type": "array", "default": [], "source": "query", "selector": "thead tr", "query": { "cells": { "type": "array", "default": [], "source": "query", "selector": "td,th", "query": { "content": { "type": "rich-text", "source": "rich-text" }, "tag": { "type": "string", "default": "td", "source": "tag" }, "scope": { "type": "string", "source": "attribute", "attribute": "scope" }, "align": { "type": "string", "source": "attribute", "attribute": "data-align" }, "colspan": { "type": "string", "source": "attribute", "attribute": "colspan" }, "rowspan": { "type": "string", "source": "attribute", "attribute": "rowspan" } } } } }, "body": { "type": "array", "default": [], "source": "query", "selector": "tbody tr", "query": { "cells": { "type": "array", "default": [], "source": "query", "selector": "td,th", "query": { "content": { "type": "rich-text", "source": "rich-text" }, "tag": { "type": "string", "default": "td", "source": "tag" }, "scope": { "type": "string", "source": "attribute", "attribute": "scope" }, "align": { "type": "string", "source": "attribute", "attribute": "data-align" }, "colspan": { "type": "string", "source": "attribute", "attribute": "colspan" }, "rowspan": { "type": "string", "source": "attribute", "attribute": "rowspan" } } } } }, "foot": { "type": "array", "default": [], "source": "query", "selector": "tfoot tr", "query": { "cells": { "type": "array", "default": [], "source": "query", "selector": "td,th", "query": { "content": { "type": "rich-text", "source": "rich-text" }, "tag": { "type": "string", "default": "td", "source": "tag" }, "scope": { "type": "string", "source": "attribute", "attribute": "scope" }, "align": { "type": "string", "source": "attribute", "attribute": "data-align" }, "colspan": { "type": "string", "source": "attribute", "attribute": "colspan" }, "rowspan": { "type": "string", "source": "attribute", "attribute": "rowspan" } } } } } }, "supports": { "anchor": true, "align": true, "color": { "__experimentalSkipSerialization": true, "gradients": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "spacing": { "margin": true, "padding": true, "__experimentalDefaultControls": { "margin": false, "padding": false } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontStyle": true, "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalDefaultControls": { "fontSize": true } }, "__experimentalBorder": { "__experimentalSkipSerialization": true, "color": true, "style": true, "width": true, "__experimentalDefaultControls": { "color": true, "style": true, "width": true } }, "interactivity": { "clientNavigation": true } }, "selectors": { "root": ".wp-block-table > table", "spacing": ".wp-block-table" }, "styles": [ { "name": "regular", "label": "Default", "isDefault": true }, { "name": "stripes", "label": "Stripes" } ], "editorStyle": "wp-block-table-editor", "style": "wp-block-table" } PKt\BNKtable/editor.min.cssnu[.wp-block[data-align=center]>.wp-block-table,.wp-block[data-align=left]>.wp-block-table,.wp-block[data-align=right]>.wp-block-table{height:auto}.wp-block[data-align=center]>.wp-block-table table,.wp-block[data-align=left]>.wp-block-table table,.wp-block[data-align=right]>.wp-block-table table{width:auto}.wp-block[data-align=center]>.wp-block-table td,.wp-block[data-align=center]>.wp-block-table th,.wp-block[data-align=left]>.wp-block-table td,.wp-block[data-align=left]>.wp-block-table th,.wp-block[data-align=right]>.wp-block-table td,.wp-block[data-align=right]>.wp-block-table th{word-break:break-word}.wp-block[data-align=center]>.wp-block-table{text-align:initial}.wp-block[data-align=center]>.wp-block-table table{margin:0 auto}.wp-block-table td,.wp-block-table th{border:1px solid;padding:.5em}.wp-block-table td.is-selected,.wp-block-table th.is-selected{border-color:var(--wp-admin-theme-color);border-style:double;box-shadow:inset 0 0 0 1px var(--wp-admin-theme-color)}.wp-block-table table.has-individual-borders td,.wp-block-table table.has-individual-borders th,.wp-block-table table.has-individual-borders tr,.wp-block-table table.has-individual-borders>*{border:1px solid}.blocks-table__placeholder-form.blocks-table__placeholder-form{align-items:flex-start;display:flex;flex-direction:column;gap:8px}@media (min-width:782px){.blocks-table__placeholder-form.blocks-table__placeholder-form{align-items:flex-end;flex-direction:row}}.blocks-table__placeholder-input{width:112px}PKt\)) table/theme.min.cssnu[.wp-block-table{margin:0 0 1em}.wp-block-table td,.wp-block-table th{word-break:normal}.wp-block-table :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-table :where(figcaption){color:#ffffffa6}PKt\LLctable/theme.cssnu[.wp-block-table{ margin:0 0 1em; } .wp-block-table td,.wp-block-table th{ word-break:normal; } .wp-block-table :where(figcaption){ color:#555; font-size:13px; text-align:center; } .is-dark-theme .wp-block-table :where(figcaption){ color:#ffffffa6; }PKt\Ptable/style.min.cssnu[.wp-block-table{overflow-x:auto}.wp-block-table table{border-collapse:collapse;width:100%}.wp-block-table thead{border-bottom:3px solid}.wp-block-table tfoot{border-top:3px solid}.wp-block-table td,.wp-block-table th{border:1px solid;padding:.5em}.wp-block-table .has-fixed-layout{table-layout:fixed;width:100%}.wp-block-table .has-fixed-layout td,.wp-block-table .has-fixed-layout th{word-break:break-word}.wp-block-table.aligncenter,.wp-block-table.alignleft,.wp-block-table.alignright{display:table;width:auto}.wp-block-table.aligncenter td,.wp-block-table.aligncenter th,.wp-block-table.alignleft td,.wp-block-table.alignleft th,.wp-block-table.alignright td,.wp-block-table.alignright th{word-break:break-word}.wp-block-table .has-subtle-light-gray-background-color{background-color:#f3f4f5}.wp-block-table .has-subtle-pale-green-background-color{background-color:#e9fbe5}.wp-block-table .has-subtle-pale-blue-background-color{background-color:#e7f5fe}.wp-block-table .has-subtle-pale-pink-background-color{background-color:#fcf0ef}.wp-block-table.is-style-stripes{background-color:initial;border-bottom:1px solid #f0f0f0;border-collapse:inherit;border-spacing:0}.wp-block-table.is-style-stripes tbody tr:nth-child(odd){background-color:#f0f0f0}.wp-block-table.is-style-stripes.has-subtle-light-gray-background-color tbody tr:nth-child(odd){background-color:#f3f4f5}.wp-block-table.is-style-stripes.has-subtle-pale-green-background-color tbody tr:nth-child(odd){background-color:#e9fbe5}.wp-block-table.is-style-stripes.has-subtle-pale-blue-background-color tbody tr:nth-child(odd){background-color:#e7f5fe}.wp-block-table.is-style-stripes.has-subtle-pale-pink-background-color tbody tr:nth-child(odd){background-color:#fcf0ef}.wp-block-table.is-style-stripes td,.wp-block-table.is-style-stripes th{border-color:#0000}.wp-block-table .has-border-color td,.wp-block-table .has-border-color th,.wp-block-table .has-border-color tr,.wp-block-table .has-border-color>*{border-color:inherit}.wp-block-table table[style*=border-top-color] tr:first-child,.wp-block-table table[style*=border-top-color] tr:first-child td,.wp-block-table table[style*=border-top-color] tr:first-child th,.wp-block-table table[style*=border-top-color]>*,.wp-block-table table[style*=border-top-color]>* td,.wp-block-table table[style*=border-top-color]>* th{border-top-color:inherit}.wp-block-table table[style*=border-top-color] tr:not(:first-child){border-top-color:initial}.wp-block-table table[style*=border-right-color] td:last-child,.wp-block-table table[style*=border-right-color] th,.wp-block-table table[style*=border-right-color] tr,.wp-block-table table[style*=border-right-color]>*{border-right-color:inherit}.wp-block-table table[style*=border-bottom-color] tr:last-child,.wp-block-table table[style*=border-bottom-color] tr:last-child td,.wp-block-table table[style*=border-bottom-color] tr:last-child th,.wp-block-table table[style*=border-bottom-color]>*,.wp-block-table table[style*=border-bottom-color]>* td,.wp-block-table table[style*=border-bottom-color]>* th{border-bottom-color:inherit}.wp-block-table table[style*=border-bottom-color] tr:not(:last-child){border-bottom-color:initial}.wp-block-table table[style*=border-left-color] td:first-child,.wp-block-table table[style*=border-left-color] th,.wp-block-table table[style*=border-left-color] tr,.wp-block-table table[style*=border-left-color]>*{border-left-color:inherit}.wp-block-table table[style*=border-style] td,.wp-block-table table[style*=border-style] th,.wp-block-table table[style*=border-style] tr,.wp-block-table table[style*=border-style]>*{border-style:inherit}.wp-block-table table[style*=border-width] td,.wp-block-table table[style*=border-width] th,.wp-block-table table[style*=border-width] tr,.wp-block-table table[style*=border-width]>*{border-style:inherit;border-width:inherit}PKt\(ytable/style-rtl.cssnu[.wp-block-table{ overflow-x:auto; } .wp-block-table table{ border-collapse:collapse; width:100%; } .wp-block-table thead{ border-bottom:3px solid; } .wp-block-table tfoot{ border-top:3px solid; } .wp-block-table td,.wp-block-table th{ border:1px solid; padding:.5em; } .wp-block-table .has-fixed-layout{ table-layout:fixed; width:100%; } .wp-block-table .has-fixed-layout td,.wp-block-table .has-fixed-layout th{ word-break:break-word; } .wp-block-table.aligncenter,.wp-block-table.alignleft,.wp-block-table.alignright{ display:table; width:auto; } .wp-block-table.aligncenter td,.wp-block-table.aligncenter th,.wp-block-table.alignleft td,.wp-block-table.alignleft th,.wp-block-table.alignright td,.wp-block-table.alignright th{ word-break:break-word; } .wp-block-table .has-subtle-light-gray-background-color{ background-color:#f3f4f5; } .wp-block-table .has-subtle-pale-green-background-color{ background-color:#e9fbe5; } .wp-block-table .has-subtle-pale-blue-background-color{ background-color:#e7f5fe; } .wp-block-table .has-subtle-pale-pink-background-color{ background-color:#fcf0ef; } .wp-block-table.is-style-stripes{ background-color:initial; border-bottom:1px solid #f0f0f0; border-collapse:inherit; border-spacing:0; } .wp-block-table.is-style-stripes tbody tr:nth-child(odd){ background-color:#f0f0f0; } .wp-block-table.is-style-stripes.has-subtle-light-gray-background-color tbody tr:nth-child(odd){ background-color:#f3f4f5; } .wp-block-table.is-style-stripes.has-subtle-pale-green-background-color tbody tr:nth-child(odd){ background-color:#e9fbe5; } .wp-block-table.is-style-stripes.has-subtle-pale-blue-background-color tbody tr:nth-child(odd){ background-color:#e7f5fe; } .wp-block-table.is-style-stripes.has-subtle-pale-pink-background-color tbody tr:nth-child(odd){ background-color:#fcf0ef; } .wp-block-table.is-style-stripes td,.wp-block-table.is-style-stripes th{ border-color:#0000; } .wp-block-table .has-border-color td,.wp-block-table .has-border-color th,.wp-block-table .has-border-color tr,.wp-block-table .has-border-color>*{ border-color:inherit; } .wp-block-table table[style*=border-top-color] tr:first-child,.wp-block-table table[style*=border-top-color] tr:first-child td,.wp-block-table table[style*=border-top-color] tr:first-child th,.wp-block-table table[style*=border-top-color]>*,.wp-block-table table[style*=border-top-color]>* td,.wp-block-table table[style*=border-top-color]>* th{ border-top-color:inherit; } .wp-block-table table[style*=border-top-color] tr:not(:first-child){ border-top-color:initial; } .wp-block-table table[style*=border-right-color] td:last-child,.wp-block-table table[style*=border-right-color] th,.wp-block-table table[style*=border-right-color] tr,.wp-block-table table[style*=border-right-color]>*{ border-left-color:inherit; } .wp-block-table table[style*=border-bottom-color] tr:last-child,.wp-block-table table[style*=border-bottom-color] tr:last-child td,.wp-block-table table[style*=border-bottom-color] tr:last-child th,.wp-block-table table[style*=border-bottom-color]>*,.wp-block-table table[style*=border-bottom-color]>* td,.wp-block-table table[style*=border-bottom-color]>* th{ border-bottom-color:inherit; } .wp-block-table table[style*=border-bottom-color] tr:not(:last-child){ border-bottom-color:initial; } .wp-block-table table[style*=border-left-color] td:first-child,.wp-block-table table[style*=border-left-color] th,.wp-block-table table[style*=border-left-color] tr,.wp-block-table table[style*=border-left-color]>*{ border-right-color:inherit; } .wp-block-table table[style*=border-style] td,.wp-block-table table[style*=border-style] th,.wp-block-table table[style*=border-style] tr,.wp-block-table table[style*=border-style]>*{ border-style:inherit; } .wp-block-table table[style*=border-width] td,.wp-block-table table[style*=border-width] th,.wp-block-table table[style*=border-width] tr,.wp-block-table table[style*=border-width]>*{ border-style:inherit; border-width:inherit; }PKt\K99table/editor-rtl.cssnu[.wp-block[data-align=center]>.wp-block-table,.wp-block[data-align=left]>.wp-block-table,.wp-block[data-align=right]>.wp-block-table{ height:auto; } .wp-block[data-align=center]>.wp-block-table table,.wp-block[data-align=left]>.wp-block-table table,.wp-block[data-align=right]>.wp-block-table table{ width:auto; } .wp-block[data-align=center]>.wp-block-table td,.wp-block[data-align=center]>.wp-block-table th,.wp-block[data-align=left]>.wp-block-table td,.wp-block[data-align=left]>.wp-block-table th,.wp-block[data-align=right]>.wp-block-table td,.wp-block[data-align=right]>.wp-block-table th{ word-break:break-word; } .wp-block[data-align=center]>.wp-block-table{ text-align:initial; } .wp-block[data-align=center]>.wp-block-table table{ margin:0 auto; } .wp-block-table td,.wp-block-table th{ border:1px solid; padding:.5em; } .wp-block-table td.is-selected,.wp-block-table th.is-selected{ border-color:var(--wp-admin-theme-color); border-style:double; box-shadow:inset 0 0 0 1px var(--wp-admin-theme-color); } .wp-block-table table.has-individual-borders td,.wp-block-table table.has-individual-borders th,.wp-block-table table.has-individual-borders tr,.wp-block-table table.has-individual-borders>*{ border:1px solid; } .blocks-table__placeholder-form.blocks-table__placeholder-form{ align-items:flex-start; display:flex; flex-direction:column; gap:8px; } @media (min-width:782px){ .blocks-table__placeholder-form.blocks-table__placeholder-form{ align-items:flex-end; flex-direction:row; } } .blocks-table__placeholder-input{ width:112px; }PKt\LLctable/theme-rtl.cssnu[.wp-block-table{ margin:0 0 1em; } .wp-block-table td,.wp-block-table th{ word-break:normal; } .wp-block-table :where(figcaption){ color:#555; font-size:13px; text-align:center; } .is-dark-theme .wp-block-table :where(figcaption){ color:#ffffffa6; }PKt\e9table/style.cssnu[.wp-block-table{ overflow-x:auto; } .wp-block-table table{ border-collapse:collapse; width:100%; } .wp-block-table thead{ border-bottom:3px solid; } .wp-block-table tfoot{ border-top:3px solid; } .wp-block-table td,.wp-block-table th{ border:1px solid; padding:.5em; } .wp-block-table .has-fixed-layout{ table-layout:fixed; width:100%; } .wp-block-table .has-fixed-layout td,.wp-block-table .has-fixed-layout th{ word-break:break-word; } .wp-block-table.aligncenter,.wp-block-table.alignleft,.wp-block-table.alignright{ display:table; width:auto; } .wp-block-table.aligncenter td,.wp-block-table.aligncenter th,.wp-block-table.alignleft td,.wp-block-table.alignleft th,.wp-block-table.alignright td,.wp-block-table.alignright th{ word-break:break-word; } .wp-block-table .has-subtle-light-gray-background-color{ background-color:#f3f4f5; } .wp-block-table .has-subtle-pale-green-background-color{ background-color:#e9fbe5; } .wp-block-table .has-subtle-pale-blue-background-color{ background-color:#e7f5fe; } .wp-block-table .has-subtle-pale-pink-background-color{ background-color:#fcf0ef; } .wp-block-table.is-style-stripes{ background-color:initial; border-bottom:1px solid #f0f0f0; border-collapse:inherit; border-spacing:0; } .wp-block-table.is-style-stripes tbody tr:nth-child(odd){ background-color:#f0f0f0; } .wp-block-table.is-style-stripes.has-subtle-light-gray-background-color tbody tr:nth-child(odd){ background-color:#f3f4f5; } .wp-block-table.is-style-stripes.has-subtle-pale-green-background-color tbody tr:nth-child(odd){ background-color:#e9fbe5; } .wp-block-table.is-style-stripes.has-subtle-pale-blue-background-color tbody tr:nth-child(odd){ background-color:#e7f5fe; } .wp-block-table.is-style-stripes.has-subtle-pale-pink-background-color tbody tr:nth-child(odd){ background-color:#fcf0ef; } .wp-block-table.is-style-stripes td,.wp-block-table.is-style-stripes th{ border-color:#0000; } .wp-block-table .has-border-color td,.wp-block-table .has-border-color th,.wp-block-table .has-border-color tr,.wp-block-table .has-border-color>*{ border-color:inherit; } .wp-block-table table[style*=border-top-color] tr:first-child,.wp-block-table table[style*=border-top-color] tr:first-child td,.wp-block-table table[style*=border-top-color] tr:first-child th,.wp-block-table table[style*=border-top-color]>*,.wp-block-table table[style*=border-top-color]>* td,.wp-block-table table[style*=border-top-color]>* th{ border-top-color:inherit; } .wp-block-table table[style*=border-top-color] tr:not(:first-child){ border-top-color:initial; } .wp-block-table table[style*=border-right-color] td:last-child,.wp-block-table table[style*=border-right-color] th,.wp-block-table table[style*=border-right-color] tr,.wp-block-table table[style*=border-right-color]>*{ border-right-color:inherit; } .wp-block-table table[style*=border-bottom-color] tr:last-child,.wp-block-table table[style*=border-bottom-color] tr:last-child td,.wp-block-table table[style*=border-bottom-color] tr:last-child th,.wp-block-table table[style*=border-bottom-color]>*,.wp-block-table table[style*=border-bottom-color]>* td,.wp-block-table table[style*=border-bottom-color]>* th{ border-bottom-color:inherit; } .wp-block-table table[style*=border-bottom-color] tr:not(:last-child){ border-bottom-color:initial; } .wp-block-table table[style*=border-left-color] td:first-child,.wp-block-table table[style*=border-left-color] th,.wp-block-table table[style*=border-left-color] tr,.wp-block-table table[style*=border-left-color]>*{ border-left-color:inherit; } .wp-block-table table[style*=border-style] td,.wp-block-table table[style*=border-style] th,.wp-block-table table[style*=border-style] tr,.wp-block-table table[style*=border-style]>*{ border-style:inherit; } .wp-block-table table[style*=border-width] td,.wp-block-table table[style*=border-width] th,.wp-block-table table[style*=border-width] tr,.wp-block-table table[style*=border-width]>*{ border-style:inherit; border-width:inherit; }PKt\)) table/theme-rtl.min.cssnu[.wp-block-table{margin:0 0 1em}.wp-block-table td,.wp-block-table th{word-break:normal}.wp-block-table :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-table :where(figcaption){color:#ffffffa6}PKt\#gċtable/style-rtl.min.cssnu[.wp-block-table{overflow-x:auto}.wp-block-table table{border-collapse:collapse;width:100%}.wp-block-table thead{border-bottom:3px solid}.wp-block-table tfoot{border-top:3px solid}.wp-block-table td,.wp-block-table th{border:1px solid;padding:.5em}.wp-block-table .has-fixed-layout{table-layout:fixed;width:100%}.wp-block-table .has-fixed-layout td,.wp-block-table .has-fixed-layout th{word-break:break-word}.wp-block-table.aligncenter,.wp-block-table.alignleft,.wp-block-table.alignright{display:table;width:auto}.wp-block-table.aligncenter td,.wp-block-table.aligncenter th,.wp-block-table.alignleft td,.wp-block-table.alignleft th,.wp-block-table.alignright td,.wp-block-table.alignright th{word-break:break-word}.wp-block-table .has-subtle-light-gray-background-color{background-color:#f3f4f5}.wp-block-table .has-subtle-pale-green-background-color{background-color:#e9fbe5}.wp-block-table .has-subtle-pale-blue-background-color{background-color:#e7f5fe}.wp-block-table .has-subtle-pale-pink-background-color{background-color:#fcf0ef}.wp-block-table.is-style-stripes{background-color:initial;border-bottom:1px solid #f0f0f0;border-collapse:inherit;border-spacing:0}.wp-block-table.is-style-stripes tbody tr:nth-child(odd){background-color:#f0f0f0}.wp-block-table.is-style-stripes.has-subtle-light-gray-background-color tbody tr:nth-child(odd){background-color:#f3f4f5}.wp-block-table.is-style-stripes.has-subtle-pale-green-background-color tbody tr:nth-child(odd){background-color:#e9fbe5}.wp-block-table.is-style-stripes.has-subtle-pale-blue-background-color tbody tr:nth-child(odd){background-color:#e7f5fe}.wp-block-table.is-style-stripes.has-subtle-pale-pink-background-color tbody tr:nth-child(odd){background-color:#fcf0ef}.wp-block-table.is-style-stripes td,.wp-block-table.is-style-stripes th{border-color:#0000}.wp-block-table .has-border-color td,.wp-block-table .has-border-color th,.wp-block-table .has-border-color tr,.wp-block-table .has-border-color>*{border-color:inherit}.wp-block-table table[style*=border-top-color] tr:first-child,.wp-block-table table[style*=border-top-color] tr:first-child td,.wp-block-table table[style*=border-top-color] tr:first-child th,.wp-block-table table[style*=border-top-color]>*,.wp-block-table table[style*=border-top-color]>* td,.wp-block-table table[style*=border-top-color]>* th{border-top-color:inherit}.wp-block-table table[style*=border-top-color] tr:not(:first-child){border-top-color:initial}.wp-block-table table[style*=border-right-color] td:last-child,.wp-block-table table[style*=border-right-color] th,.wp-block-table table[style*=border-right-color] tr,.wp-block-table table[style*=border-right-color]>*{border-left-color:inherit}.wp-block-table table[style*=border-bottom-color] tr:last-child,.wp-block-table table[style*=border-bottom-color] tr:last-child td,.wp-block-table table[style*=border-bottom-color] tr:last-child th,.wp-block-table table[style*=border-bottom-color]>*,.wp-block-table table[style*=border-bottom-color]>* td,.wp-block-table table[style*=border-bottom-color]>* th{border-bottom-color:inherit}.wp-block-table table[style*=border-bottom-color] tr:not(:last-child){border-bottom-color:initial}.wp-block-table table[style*=border-left-color] td:first-child,.wp-block-table table[style*=border-left-color] th,.wp-block-table table[style*=border-left-color] tr,.wp-block-table table[style*=border-left-color]>*{border-right-color:inherit}.wp-block-table table[style*=border-style] td,.wp-block-table table[style*=border-style] th,.wp-block-table table[style*=border-style] tr,.wp-block-table table[style*=border-style]>*{border-style:inherit}.wp-block-table table[style*=border-width] td,.wp-block-table table[style*=border-width] th,.wp-block-table table[style*=border-width] tr,.wp-block-table table[style*=border-width]>*{border-style:inherit;border-width:inherit}PKt\BNKtable/editor-rtl.min.cssnu[.wp-block[data-align=center]>.wp-block-table,.wp-block[data-align=left]>.wp-block-table,.wp-block[data-align=right]>.wp-block-table{height:auto}.wp-block[data-align=center]>.wp-block-table table,.wp-block[data-align=left]>.wp-block-table table,.wp-block[data-align=right]>.wp-block-table table{width:auto}.wp-block[data-align=center]>.wp-block-table td,.wp-block[data-align=center]>.wp-block-table th,.wp-block[data-align=left]>.wp-block-table td,.wp-block[data-align=left]>.wp-block-table th,.wp-block[data-align=right]>.wp-block-table td,.wp-block[data-align=right]>.wp-block-table th{word-break:break-word}.wp-block[data-align=center]>.wp-block-table{text-align:initial}.wp-block[data-align=center]>.wp-block-table table{margin:0 auto}.wp-block-table td,.wp-block-table th{border:1px solid;padding:.5em}.wp-block-table td.is-selected,.wp-block-table th.is-selected{border-color:var(--wp-admin-theme-color);border-style:double;box-shadow:inset 0 0 0 1px var(--wp-admin-theme-color)}.wp-block-table table.has-individual-borders td,.wp-block-table table.has-individual-borders th,.wp-block-table table.has-individual-borders tr,.wp-block-table table.has-individual-borders>*{border:1px solid}.blocks-table__placeholder-form.blocks-table__placeholder-form{align-items:flex-start;display:flex;flex-direction:column;gap:8px}@media (min-width:782px){.blocks-table__placeholder-form.blocks-table__placeholder-form{align-items:flex-end;flex-direction:row}}.blocks-table__placeholder-input{width:112px}PKt\K99table/editor.cssnu[.wp-block[data-align=center]>.wp-block-table,.wp-block[data-align=left]>.wp-block-table,.wp-block[data-align=right]>.wp-block-table{ height:auto; } .wp-block[data-align=center]>.wp-block-table table,.wp-block[data-align=left]>.wp-block-table table,.wp-block[data-align=right]>.wp-block-table table{ width:auto; } .wp-block[data-align=center]>.wp-block-table td,.wp-block[data-align=center]>.wp-block-table th,.wp-block[data-align=left]>.wp-block-table td,.wp-block[data-align=left]>.wp-block-table th,.wp-block[data-align=right]>.wp-block-table td,.wp-block[data-align=right]>.wp-block-table th{ word-break:break-word; } .wp-block[data-align=center]>.wp-block-table{ text-align:initial; } .wp-block[data-align=center]>.wp-block-table table{ margin:0 auto; } .wp-block-table td,.wp-block-table th{ border:1px solid; padding:.5em; } .wp-block-table td.is-selected,.wp-block-table th.is-selected{ border-color:var(--wp-admin-theme-color); border-style:double; box-shadow:inset 0 0 0 1px var(--wp-admin-theme-color); } .wp-block-table table.has-individual-borders td,.wp-block-table table.has-individual-borders th,.wp-block-table table.has-individual-borders tr,.wp-block-table table.has-individual-borders>*{ border:1px solid; } .blocks-table__placeholder-form.blocks-table__placeholder-form{ align-items:flex-start; display:flex; flex-direction:column; gap:8px; } @media (min-width:782px){ .blocks-table__placeholder-form.blocks-table__placeholder-form{ align-items:flex-end; flex-direction:row; } } .blocks-table__placeholder-input{ width:112px; }PKt\W^comments-pagination/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/comments-pagination", "title": "Comments Pagination", "category": "theme", "parent": [ "core/comments" ], "allowedBlocks": [ "core/comments-pagination-previous", "core/comments-pagination-numbers", "core/comments-pagination-next" ], "description": "Displays a paginated navigation to next/previous set of comments, when applicable.", "textdomain": "default", "attributes": { "paginationArrow": { "type": "string", "default": "none" } }, "example": { "attributes": { "paginationArrow": "none" } }, "providesContext": { "comments/paginationArrow": "paginationArrow" }, "supports": { "align": true, "reusable": false, "html": false, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true, "link": true } }, "layout": { "allowSwitching": false, "allowInheriting": false, "default": { "type": "flex" } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-comments-pagination-editor", "style": "wp-block-comments-pagination" } PKt\j"comments-pagination/editor.min.cssnu[.wp-block[data-align=center]>.wp-block-comments-pagination{justify-content:center}:where(.editor-styles-wrapper) .wp-block-comments-pagination{max-width:100%}:where(.editor-styles-wrapper) .wp-block-comments-pagination.block-editor-block-list__layout{margin:0}.wp-block-comments-pagination>.wp-block-comments-pagination-next,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers,.wp-block-comments-pagination>.wp-block-comments-pagination-previous{font-size:inherit;margin:.5em .5em .5em 0}.wp-block-comments-pagination>.wp-block-comments-pagination-next:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-previous:last-child{margin-right:0}PKt\8{X!comments-pagination/style.min.cssnu[.wp-block-comments-pagination>.wp-block-comments-pagination-next,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers,.wp-block-comments-pagination>.wp-block-comments-pagination-previous{font-size:inherit;margin-bottom:.5em;margin-right:.5em}.wp-block-comments-pagination>.wp-block-comments-pagination-next:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-previous:last-child{margin-right:0}.wp-block-comments-pagination .wp-block-comments-pagination-previous-arrow{display:inline-block;margin-right:1ch}.wp-block-comments-pagination .wp-block-comments-pagination-previous-arrow:not(.is-arrow-chevron){transform:scaleX(1)}.wp-block-comments-pagination .wp-block-comments-pagination-next-arrow{display:inline-block;margin-left:1ch}.wp-block-comments-pagination .wp-block-comments-pagination-next-arrow:not(.is-arrow-chevron){transform:scaleX(1)}.wp-block-comments-pagination.aligncenter{justify-content:center}PKt\ǝ::!comments-pagination/style-rtl.cssnu[.wp-block-comments-pagination>.wp-block-comments-pagination-next,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers,.wp-block-comments-pagination>.wp-block-comments-pagination-previous{ font-size:inherit; margin-bottom:.5em; margin-right:.5em; } .wp-block-comments-pagination>.wp-block-comments-pagination-next:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-previous:last-child{ margin-right:0; } .wp-block-comments-pagination .wp-block-comments-pagination-previous-arrow{ display:inline-block; margin-left:1ch; } .wp-block-comments-pagination .wp-block-comments-pagination-previous-arrow:not(.is-arrow-chevron){ transform:scaleX(-1);; } .wp-block-comments-pagination .wp-block-comments-pagination-next-arrow{ display:inline-block; margin-right:1ch; } .wp-block-comments-pagination .wp-block-comments-pagination-next-arrow:not(.is-arrow-chevron){ transform:scaleX(-1);; } .wp-block-comments-pagination.aligncenter{ justify-content:center; }PKt\%a77"comments-pagination/editor-rtl.cssnu[.wp-block[data-align=center]>.wp-block-comments-pagination{ justify-content:center; } :where(.editor-styles-wrapper) .wp-block-comments-pagination{ max-width:100%; } :where(.editor-styles-wrapper) .wp-block-comments-pagination.block-editor-block-list__layout{ margin:0; } .wp-block-comments-pagination>.wp-block-comments-pagination-next,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers,.wp-block-comments-pagination>.wp-block-comments-pagination-previous{ font-size:inherit; margin-bottom:.5em; margin-right:.5em; margin-top:.5em; } .wp-block-comments-pagination>.wp-block-comments-pagination-next:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-previous:last-child{ margin-right:0; }PKt\66comments-pagination/style.cssnu[.wp-block-comments-pagination>.wp-block-comments-pagination-next,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers,.wp-block-comments-pagination>.wp-block-comments-pagination-previous{ font-size:inherit; margin-bottom:.5em; margin-right:.5em; } .wp-block-comments-pagination>.wp-block-comments-pagination-next:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-previous:last-child{ margin-right:0; } .wp-block-comments-pagination .wp-block-comments-pagination-previous-arrow{ display:inline-block; margin-right:1ch; } .wp-block-comments-pagination .wp-block-comments-pagination-previous-arrow:not(.is-arrow-chevron){ transform:scaleX(1); } .wp-block-comments-pagination .wp-block-comments-pagination-next-arrow{ display:inline-block; margin-left:1ch; } .wp-block-comments-pagination .wp-block-comments-pagination-next-arrow:not(.is-arrow-chevron){ transform:scaleX(1); } .wp-block-comments-pagination.aligncenter{ justify-content:center; }PKt\GW%comments-pagination/style-rtl.min.cssnu[.wp-block-comments-pagination>.wp-block-comments-pagination-next,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers,.wp-block-comments-pagination>.wp-block-comments-pagination-previous{font-size:inherit;margin-bottom:.5em;margin-right:.5em}.wp-block-comments-pagination>.wp-block-comments-pagination-next:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-previous:last-child{margin-right:0}.wp-block-comments-pagination .wp-block-comments-pagination-previous-arrow{display:inline-block;margin-left:1ch}.wp-block-comments-pagination .wp-block-comments-pagination-previous-arrow:not(.is-arrow-chevron){transform:scaleX(-1)}.wp-block-comments-pagination .wp-block-comments-pagination-next-arrow{display:inline-block;margin-right:1ch}.wp-block-comments-pagination .wp-block-comments-pagination-next-arrow:not(.is-arrow-chevron){transform:scaleX(-1)}.wp-block-comments-pagination.aligncenter{justify-content:center}PKt\#&comments-pagination/editor-rtl.min.cssnu[.wp-block[data-align=center]>.wp-block-comments-pagination{justify-content:center}:where(.editor-styles-wrapper) .wp-block-comments-pagination{max-width:100%}:where(.editor-styles-wrapper) .wp-block-comments-pagination.block-editor-block-list__layout{margin:0}.wp-block-comments-pagination>.wp-block-comments-pagination-next,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers,.wp-block-comments-pagination>.wp-block-comments-pagination-previous{font-size:inherit;margin-bottom:.5em;margin-right:.5em;margin-top:.5em}.wp-block-comments-pagination>.wp-block-comments-pagination-next:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-previous:last-child{margin-right:0}PKt\*ocomments-pagination/editor.cssnu[.wp-block[data-align=center]>.wp-block-comments-pagination{ justify-content:center; } :where(.editor-styles-wrapper) .wp-block-comments-pagination{ max-width:100%; } :where(.editor-styles-wrapper) .wp-block-comments-pagination.block-editor-block-list__layout{ margin:0; } .wp-block-comments-pagination>.wp-block-comments-pagination-next,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers,.wp-block-comments-pagination>.wp-block-comments-pagination-previous{ font-size:inherit; margin:.5em .5em .5em 0; } .wp-block-comments-pagination>.wp-block-comments-pagination-next:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-numbers:last-child,.wp-block-comments-pagination>.wp-block-comments-pagination-previous:last-child{ margin-right:0; }PKt\fJ$$post-featured-image.phpnu[context['postId'] ) ) { return ''; } $post_ID = $block->context['postId']; $is_link = isset( $attributes['isLink'] ) && $attributes['isLink']; $size_slug = isset( $attributes['sizeSlug'] ) ? $attributes['sizeSlug'] : 'post-thumbnail'; $attr = get_block_core_post_featured_image_border_attributes( $attributes ); $overlay_markup = get_block_core_post_featured_image_overlay_element_markup( $attributes ); if ( $is_link ) { if ( get_the_title( $post_ID ) ) { $attr['alt'] = trim( strip_tags( get_the_title( $post_ID ) ) ); } else { $attr['alt'] = sprintf( // translators: %d is the post ID. __( 'Untitled post %d' ), $post_ID ); } } $extra_styles = ''; // Aspect ratio with a height set needs to override the default width/height. if ( ! empty( $attributes['aspectRatio'] ) ) { $extra_styles .= 'width:100%;height:100%;'; } elseif ( ! empty( $attributes['height'] ) ) { $extra_styles .= "height:{$attributes['height']};"; } if ( ! empty( $attributes['scale'] ) ) { $extra_styles .= "object-fit:{$attributes['scale']};"; } if ( ! empty( $attributes['style']['shadow'] ) ) { $shadow_styles = wp_style_engine_get_styles( array( 'shadow' => $attributes['style']['shadow'] ) ); if ( ! empty( $shadow_styles['css'] ) ) { $extra_styles .= $shadow_styles['css']; } } if ( ! empty( $extra_styles ) ) { $attr['style'] = empty( $attr['style'] ) ? $extra_styles : $attr['style'] . $extra_styles; } $featured_image = get_the_post_thumbnail( $post_ID, $size_slug, $attr ); // Get the first image from the post. if ( $attributes['useFirstImageFromPost'] && ! $featured_image ) { $content_post = get_post( $post_ID ); $content = $content_post->post_content; $processor = new WP_HTML_Tag_Processor( $content ); /* * Transfer the image tag from the post into a new text snippet. * Because the HTML API doesn't currently expose a way to extract * HTML substrings this is necessary as a workaround. Of note, this * is different than directly extracting the IMG tag: * - If there are duplicate attributes in the source there will only be one in the output. * - If there are single-quoted or unquoted attributes they will be double-quoted in the output. * - If there are named character references in the attribute values they may be replaced with their direct code points. E.g. `…` becomes `…`. * In the future there will likely be a mechanism to copy snippets of HTML from * one document into another, via the HTML Processor's `get_outer_html()` or * equivalent. When that happens it would be appropriate to replace this custom * code with that canonical code. */ if ( $processor->next_tag( 'img' ) ) { $tag_html = new WP_HTML_Tag_Processor( '' ); $tag_html->next_tag(); foreach ( $processor->get_attribute_names_with_prefix( '' ) as $name ) { $tag_html->set_attribute( $name, $processor->get_attribute( $name ) ); } $featured_image = $tag_html->get_updated_html(); } } if ( ! $featured_image ) { return ''; } if ( $is_link ) { $link_target = $attributes['linkTarget']; $rel = ! empty( $attributes['rel'] ) ? 'rel="' . esc_attr( $attributes['rel'] ) . '"' : ''; $height = ! empty( $attributes['height'] ) ? 'style="' . esc_attr( safecss_filter_attr( 'height:' . $attributes['height'] ) ) . '"' : ''; $featured_image = sprintf( '%5$s%6$s', get_the_permalink( $post_ID ), esc_attr( $link_target ), $rel, $height, $featured_image, $overlay_markup ); } else { $featured_image = $featured_image . $overlay_markup; } $aspect_ratio = ! empty( $attributes['aspectRatio'] ) ? esc_attr( safecss_filter_attr( 'aspect-ratio:' . $attributes['aspectRatio'] ) ) . ';' : ''; $width = ! empty( $attributes['width'] ) ? esc_attr( safecss_filter_attr( 'width:' . $attributes['width'] ) ) . ';' : ''; $height = ! empty( $attributes['height'] ) ? esc_attr( safecss_filter_attr( 'height:' . $attributes['height'] ) ) . ';' : ''; if ( ! $height && ! $width && ! $aspect_ratio ) { $wrapper_attributes = get_block_wrapper_attributes(); } else { $wrapper_attributes = get_block_wrapper_attributes( array( 'style' => $aspect_ratio . $width . $height ) ); } return "
          {$featured_image}
          "; } /** * Generate markup for the HTML element that will be used for the overlay. * * @since 6.1.0 * * @param array $attributes Block attributes. * * @return string HTML markup in string format. */ function get_block_core_post_featured_image_overlay_element_markup( $attributes ) { $has_dim_background = isset( $attributes['dimRatio'] ) && $attributes['dimRatio']; $has_gradient = isset( $attributes['gradient'] ) && $attributes['gradient']; $has_custom_gradient = isset( $attributes['customGradient'] ) && $attributes['customGradient']; $has_solid_overlay = isset( $attributes['overlayColor'] ) && $attributes['overlayColor']; $has_custom_overlay = isset( $attributes['customOverlayColor'] ) && $attributes['customOverlayColor']; $class_names = array( 'wp-block-post-featured-image__overlay' ); $styles = array(); if ( ! $has_dim_background ) { return ''; } // Apply border classes and styles. $border_attributes = get_block_core_post_featured_image_border_attributes( $attributes ); if ( ! empty( $border_attributes['class'] ) ) { $class_names[] = $border_attributes['class']; } if ( ! empty( $border_attributes['style'] ) ) { $styles[] = $border_attributes['style']; } // Apply overlay and gradient classes. if ( $has_dim_background ) { $class_names[] = 'has-background-dim'; $class_names[] = "has-background-dim-{$attributes['dimRatio']}"; } if ( $has_solid_overlay ) { $class_names[] = "has-{$attributes['overlayColor']}-background-color"; } if ( $has_gradient || $has_custom_gradient ) { $class_names[] = 'has-background-gradient'; } if ( $has_gradient ) { $class_names[] = "has-{$attributes['gradient']}-gradient-background"; } // Apply background styles. if ( $has_custom_gradient ) { $styles[] = sprintf( 'background-image: %s;', $attributes['customGradient'] ); } if ( $has_custom_overlay ) { $styles[] = sprintf( 'background-color: %s;', $attributes['customOverlayColor'] ); } return sprintf( '', esc_attr( implode( ' ', $class_names ) ), esc_attr( safecss_filter_attr( implode( ' ', $styles ) ) ) ); } /** * Generates class names and styles to apply the border support styles for * the Post Featured Image block. * * @since 6.1.0 * * @param array $attributes The block attributes. * @return array The border-related classnames and styles for the block. */ function get_block_core_post_featured_image_border_attributes( $attributes ) { $border_styles = array(); $sides = array( 'top', 'right', 'bottom', 'left' ); // Border radius. if ( isset( $attributes['style']['border']['radius'] ) ) { $border_styles['radius'] = $attributes['style']['border']['radius']; } // Border style. if ( isset( $attributes['style']['border']['style'] ) ) { $border_styles['style'] = $attributes['style']['border']['style']; } // Border width. if ( isset( $attributes['style']['border']['width'] ) ) { $border_styles['width'] = $attributes['style']['border']['width']; } // Border color. $preset_color = array_key_exists( 'borderColor', $attributes ) ? "var:preset|color|{$attributes['borderColor']}" : null; $custom_color = $attributes['style']['border']['color'] ?? null; $border_styles['color'] = $preset_color ? $preset_color : $custom_color; // Individual border styles e.g. top, left etc. foreach ( $sides as $side ) { $border = $attributes['style']['border'][ $side ] ?? null; $border_styles[ $side ] = array( 'color' => isset( $border['color'] ) ? $border['color'] : null, 'style' => isset( $border['style'] ) ? $border['style'] : null, 'width' => isset( $border['width'] ) ? $border['width'] : null, ); } $styles = wp_style_engine_get_styles( array( 'border' => $border_styles ) ); $attributes = array(); if ( ! empty( $styles['classnames'] ) ) { $attributes['class'] = $styles['classnames']; } if ( ! empty( $styles['css'] ) ) { $attributes['style'] = $styles['css']; } return $attributes; } /** * Registers the `core/post-featured-image` block on the server. * * @since 5.8.0 */ function register_block_core_post_featured_image() { register_block_type_from_metadata( __DIR__ . '/post-featured-image', array( 'render_callback' => 'render_block_core_post_featured_image', ) ); } add_action( 'init', 'register_block_core_post_featured_image' ); PKt\13comment-content/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/comment-content", "title": "Comment Content", "category": "theme", "ancestor": [ "core/comment-template" ], "description": "Displays the contents of a comment.", "textdomain": "default", "usesContext": [ "commentId" ], "attributes": { "textAlign": { "type": "string" } }, "supports": { "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } }, "spacing": { "padding": [ "horizontal", "vertical" ], "__experimentalDefaultControls": { "padding": true } }, "html": false }, "style": "wp-block-comment-content" } PKt\F||comment-content/style.min.cssnu[.comment-awaiting-moderation{display:block;font-size:.875em;line-height:1.5}.wp-block-comment-content{box-sizing:border-box}PKt\(comment-content/style-rtl.cssnu[.comment-awaiting-moderation{ display:block; font-size:.875em; line-height:1.5; } .wp-block-comment-content{ box-sizing:border-box; }PKt\(comment-content/style.cssnu[.comment-awaiting-moderation{ display:block; font-size:.875em; line-height:1.5; } .wp-block-comment-content{ box-sizing:border-box; }PKt\F||!comment-content/style-rtl.min.cssnu[.comment-awaiting-moderation{display:block;font-size:.875em;line-height:1.5}.wp-block-comment-content{box-sizing:border-box}PKt\uFF#query-pagination-numbers/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/query-pagination-numbers", "title": "Page Numbers", "category": "theme", "parent": [ "core/query-pagination" ], "description": "Displays a list of page numbers for pagination.", "textdomain": "default", "attributes": { "midSize": { "type": "number", "default": 2 } }, "usesContext": [ "queryId", "query", "enhancedPagination" ], "supports": { "reusable": false, "html": false, "color": { "gradients": true, "text": false, "__experimentalDefaultControls": { "background": true } }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true } }, "editorStyle": "wp-block-query-pagination-numbers-editor" } PKt\y֌'query-pagination-numbers/editor.min.cssnu[.wp-block-query-pagination-numbers a{text-decoration:underline}.wp-block-query-pagination-numbers .page-numbers{margin-right:2px}.wp-block-query-pagination-numbers .page-numbers:last-child{margin-right:0}PKt\'query-pagination-numbers/editor-rtl.cssnu[.wp-block-query-pagination-numbers a{ text-decoration:underline; } .wp-block-query-pagination-numbers .page-numbers{ margin-left:2px; } .wp-block-query-pagination-numbers .page-numbers:last-child{ margin-right:0; }PKt\ F+query-pagination-numbers/editor-rtl.min.cssnu[.wp-block-query-pagination-numbers a{text-decoration:underline}.wp-block-query-pagination-numbers .page-numbers{margin-left:2px}.wp-block-query-pagination-numbers .page-numbers:last-child{margin-right:0}PKt\J1Lo#query-pagination-numbers/editor.cssnu[.wp-block-query-pagination-numbers a{ text-decoration:underline; } .wp-block-query-pagination-numbers .page-numbers{ margin-right:2px; } .wp-block-query-pagination-numbers .page-numbers:last-child{ margin-right:0; }PKt\post-title/block.jsonnu[{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/post-title", "title": "Title", "category": "theme", "description": "Displays the title of a post, page, or any other content-type.", "textdomain": "default", "usesContext": [ "postId", "postType", "queryId" ], "attributes": { "textAlign": { "type": "string" }, "level": { "type": "number", "default": 2 }, "levelOptions": { "type": "array" }, "isLink": { "type": "boolean", "default": false, "role": "content" }, "rel": { "type": "string", "attribute": "rel", "default": "", "role": "content" }, "linkTarget": { "type": "string", "default": "_self", "role": "content" } }, "example": { "viewportWidth": 350 }, "supports": { "align": [ "wide", "full" ], "html": false, "color": { "gradients": true, "link": true, "__experimentalDefaultControls": { "background": true, "text": true, "link": true } }, "spacing": { "margin": true, "padding": true }, "typography": { "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, "__experimentalFontWeight": true, "__experimentalFontStyle": true, "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } }, "interactivity": { "clientNavigation": true }, "__experimentalBorder": { "radius": true, "color": true, "width": true, "style": true, "__experimentalDefaultControls": { "radius": true, "color": true, "width": true, "style": true } } }, "style": "wp-block-post-title" } PKt\flpost-title/style.min.cssnu[.wp-block-post-title{box-sizing:border-box;word-break:break-word}.wp-block-post-title :where(a){display:inline-block;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;text-decoration:inherit}PKt\,X5((post-title/style-rtl.cssnu[.wp-block-post-title{ box-sizing:border-box; word-break:break-word; } .wp-block-post-title :where(a){ display:inline-block; font-family:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; letter-spacing:inherit; line-height:inherit; text-decoration:inherit; }PKt\,X5((post-title/style.cssnu[.wp-block-post-title{ box-sizing:border-box; word-break:break-word; } .wp-block-post-title :where(a){ display:inline-block; font-family:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; letter-spacing:inherit; line-height:inherit; text-decoration:inherit; }PKt\flpost-title/style-rtl.min.cssnu[.wp-block-post-title{box-sizing:border-box;word-break:break-word}.wp-block-post-title :where(a){display:inline-block;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;text-decoration:inherit}PKt\}Erequire-static-blocks.phpnu[` or `