MDL-45898 myprofile: Redesign my profile page
[moodle.git] / user / classes / output / myprofile / category.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Defines a category in my profile page navigation.
19  *
20  * @package   core_user
21  * @copyright 2015 onwards Ankit Agarwal
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace core_user\output\myprofile;
26 defined('MOODLE_INTERNAL') || die();
28 /**
29  * Defines a category in my profile page navigation.
30  *
31  * @since     Moodle 2.9
32  * @package   core_user
33  * @copyright 2015 onwards Ankit Agarwal
34  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class category implements \renderable {
38     /**
39      * @var string Name of the category after which this category should appear.
40      */
41     private $after;
43     /**
44      * @var string Name of the category.
45      */
46     private $name;
48     /**
49      * @var string Title of the category.
50      */
51     private $title;
53     /**
54      * @var node[] Array of nodes associated with this category.
55      */
56     private $nodes = array();
58     /**
59      * @var string HTML class attribute for this category. Classes should be separated by a space, e.g. 'class1 class2'
60      */
61     private $classes;
63     /**
64      * @var array list of properties publicly accessible via __get.
65      */
66     private $properties = array('after', 'name', 'title', 'nodes', 'classes');
68     /**
69      * Constructor for category class.
70      *
71      * @param string $name Category name.
72      * @param string $title category title.
73      * @param null|string $after Name of category after which this category should appear.
74      * @param null|string $classes a list of css classes.
75      */
76     public function __construct($name, $title, $after = null, $classes = null) {
77         $this->after = $after;
78         $this->name = $name;
79         $this->title = $title;
80         $this->classes = $classes;
81     }
83     /**
84      * Add a node to this category.
85      *
86      * @param node $node node object.
87      * @see \core_user\output\myprofile\tree::add_node()
88      *
89      * @throws \coding_exception
90      */
91     public function add_node(node $node) {
92         $name = $node->name;
93         if (isset($this->nodes[$name])) {
94             throw new \coding_exception("Node with name $name already exists");
95         }
96         if ($node->parentcat !== $this->name) {
97             throw new \coding_exception("Node parent must match with the category it is added to");
98         }
99         $this->nodes[$node->name] = $node;
100     }
102     /**
103      * Sort nodes of the category in the order in which they should be displayed.
104      *
105      * @see \core_user\output\myprofile\tree::sort_categories()
106      * @throws \coding_exception
107      */
108     public function sort_nodes() {
109         $tempnodes = array();
110         $this->validate_after_order();
112         // First content noes.
113         foreach ($this->nodes as $node) {
114             $after = $node->after;
115             $content = $node->content;
116             if ($after == null && !empty($content)) {
117                 // Can go anywhere in the cat. Also show content nodes first.
118                 $tempnodes = array_merge($tempnodes, array($node->name => $node), $this->find_nodes_after($node));
119             }
120         }
122         // Now nodes with no content.
123         foreach ($this->nodes as $node) {
124             $after = $node->after;
125             $content = $node->content;
126             if ($after == null && empty($content)) {
127                 // Can go anywhere in the cat. Also show content nodes first.
128                 $tempnodes = array_merge($tempnodes, array($node->name => $node), $this->find_nodes_after($node));
129             }
130         }
132         if (count($tempnodes) !== count($this->nodes)) {
133             // Orphan nodes found.
134             throw new \coding_exception('Some of the nodes specified contains invalid \'after\' property');
135         }
136         $this->nodes = $tempnodes;
137     }
139     /**
140      * Verifies that node with content can come after node with content only . Also verifies the same thing for nodes without
141      * content.
142      * @throws \coding_exception
143      */
144     protected function validate_after_order() {
145         $nodearray = $this->nodes;
146         foreach ($this->nodes as $node) {
147             $after = $node->after;
148             if (!empty($after)) {
149                 if (empty($nodearray[$after])) {
150                     throw new \coding_exception('node {$node->name} specified contains invalid \'after\' property');
151                 } else {
152                     // Valid node found.
153                     $afternode = $nodearray[$after];
154                     $beforecontent = $node->content;
155                     $aftercontent = $afternode->content;
157                     if ((empty($beforecontent) && !empty($aftercontent)) || (!empty($beforecontent) && empty($aftercontent))) {
158                         // Only node with content are allowed after content nodes. Same goes for no content nodes.
159                         throw new \coding_exception('node {$node->name} specified contains invalid \'after\' property');
160                     }
161                 }
162             }
163         }
164     }
166     /**
167      * Given a node object find all node objects that should appear after it.
168      *
169      * @param node $node node object
170      *
171      * @return array
172      */
173     protected function find_nodes_after($node) {
174         $return = array();
175         $nodearray = $this->nodes;
176         foreach ($nodearray as $nodeelement) {
177             if ($nodeelement->after === $node->name) {
178                 // Find all nodes that comes after this node as well.
179                 $return = array_merge($return, array($nodeelement->name => $nodeelement), $this->find_nodes_after($nodeelement));
180             }
181         }
182         return $return;
183     }
185     /**
186      * Magic get method.
187      *
188      * @param string $prop property to get.
189      *
190      * @return mixed
191      * @throws \coding_exception
192      */
193     public function __get($prop) {
194         if (in_array($prop, $this->properties)) {
195             return $this->$prop;
196         }
197         throw new \coding_exception('Property "' . $prop . '" doesn\'t exist');
198     }