1 <?php
defined('SYSPATH') OR die('No direct access allowed.');
4 * Sort method to sort widgets alphabetically by displayed name
6 function sort_widgets_by_friendly_name($a, $b) {
7 return strcmp($a->name
, $b->name
) ||
strcmp($a->friendly_name
, $b->friendly_name
);
11 * A widget consists of four (or, well, five) identifying pieces of information.
13 * It has a name. The name links it to the on-disk PHP files.
15 * It has a page. This means the same widget can/will look different depending
18 * It has a user. This means that different users see the same widget
21 * It has an instance id. This means that the same user can see the same widget
22 * multiple times on the same page, with different options in each.
24 * (there's also an ID column, but it's pretty useless)
26 * When showing a widget, we first try to find a widget with the same page, user
27 * and instance id. If that's not possible, we fall back to the same page and
28 * user, but any id we can find, then to the same page but with blank user and
29 * id, and then to the 'tac/index' page with a blank user and id. When returning
30 * data from this model, nobody should have to bother about whether the widget
31 * information was returned from a fallback or not.
33 * (If you have a clever, quick, cross-database solution to do this whole thing
34 * in-database, please don't hesitate to do it)
36 * We no longer send hidden default widgets to the user. We must thus send empty
37 * widget instances that the user can copy into their own namespace.
39 * Saving an edited widget must never save to any of the fallback options. In
40 * particular, for legacy systems, that means we must never write to anything
41 * with an empty instance id. FIXME: tested?
43 * There are two add/remove pairs of functions: install/uninstall, and
44 * copy/delete. The first pair works globally, the second per-user.
47 class Ninja_widget_Model
extends Model
49 const FIRST_INSTANCE_ID
= 1; /**< When getting "any" instance of a widget, we will specifically look for this */
50 public $name; /**< The internal name of the widget */
51 public $page; /**< The page the widget is shown on */
52 public $instance_id; /**< The widget's instance id */
53 public $username; /**< The user's username */
54 public $setting; /**< The widget's settings */
56 * You should not call this constructor directly!
58 * What you're looking for is probably get.
60 function __construct($db_row) {
61 parent
::__construct();
62 if (isset($db_row['setting']['widget_title']))
63 $db_row['friendly_name'] = $db_row['setting']['widget_title'];
64 $this->db_row
= $db_row;
65 foreach ($db_row as $keys => $vals) {
71 * Fetches a list of widget names for a given page
72 * @param $page The page name
73 * @returns array of Ninja_widget_Model objects - not all of them with ID's!
75 public static function fetch_all($page)
80 $user = Auth
::instance()->get_user()->username
;
81 $db = Database
::instance();
83 # warning: cleverness!
84 # sort any rows with non-NULL instance_id first, so we can ignore the
85 # generic widget rows for widgets that have "personalized" rows
86 $res = $db->query('SELECT name, instance_id FROM ninja_widgets WHERE (page='.$db->escape($page).' AND (username IS NULL OR username='.$db->escape($user).')) OR (page=\'tac/index\' AND username IS NULL) GROUP BY name, instance_id ORDER BY instance_id DESC');
88 $seen_widgets = array();
89 foreach ($res as $row) {
90 if ($row->instance_id
=== null && isset($seen_widgets[$row->name
]))
92 $seen_widgets[$row->name
] = 1;
93 $widget = self
::get($page, $row->name
, $row->instance_id
);
94 assert('$widget !== false');
95 $widgets['widget-'.$widget->name
.'-'.$widget->instance_id
] = $widget;
97 uasort($widgets, 'sort_widgets_by_friendly_name');
102 * Fetch info on a saved widget for a page
104 * @param $page The page the widget is to be shown at
105 * @param $widget The widget name to retrieve
106 * @param $instance_id The instance_id to retrieve. Sending in NULL will create a new widget
107 * @returns A widget object, or false if none could be found matching the input
110 public static function get($page, $widget, $instance_id=null)
112 $user = Auth
::instance()->get_user();
114 $user = $user->username
;
118 $db = Database
::instance();
120 if (is_numeric($instance_id)) {
121 $subquery = 'page='.$db->escape($page).' AND username = '.$db->escape($user).' AND instance_id = '.$db->escape($instance_id);
122 $result = $db->query('SELECT * FROM ninja_widgets WHERE name='.$db->escape($widget).' AND '.$subquery.' LIMIT 1');
127 array('page' => $page, 'username' => $user, 'instance_id' => self
::FIRST_INSTANCE_ID
),
128 array('page' => $page, 'username' => $user, 'instance_id' => null),
129 array('page' => $page, 'username' => null, 'instance_id' => null),
130 array('page' => 'tac/index', 'username' => $user, 'instance_id' => self
::FIRST_INSTANCE_ID
),
131 array('page' => 'tac/index', 'username' => null, 'instance_id' => null),
133 foreach ($options as $option) {
134 $result = $db->query('SELECT * FROM ninja_widgets WHERE name='.$db->escape($widget).' AND page='.$db->escape($option['page']).' AND username '.(is_null($option['username'])?
'IS NULL':'='.$db->escape($option['username'])).' AND instance_id '.(is_null($option['instance_id'])?
'IS NULL':'='.$db->escape($option['instance_id'])).' LIMIT 1');
144 $db_row=$result->result(false)->current();
145 $db_row['setting'] = i18n
::unserialize(trim($db_row['setting']));
146 $obj = new Ninja_widget_Model($db_row);
147 if ($instance_id !== null && $obj->instance_id
=== null) {
148 // we were asked for a specific widget, but it could not be found
151 else if ($obj->instance_id
=== null) {
152 // we were asked for a widget with "any" instance_id.
153 // make sure we unset the id so we don't accidentally
157 else if ($instance_id) {
158 $obj->instance_id
= $instance_id;
161 $obj->widget
= $widget;
162 $obj->username
= $user;
168 * Save any changes to the widget. If the widget is new or not yet copied to
169 * the user's own widgets, a new ID and instance ID will be chosen.
171 public function save()
174 $user = Auth
::instance()->get_user()->username
;
175 if (!isset($this->id
) ||
!$this->instance_id
)
177 else if ($this->db_row
['name'] !== $this->name
)
179 else if ($this->db_row
['page'] !== $this->page
)
181 else if ($this->db_row
['username'] !== $user)
183 else if ($this->db_row
['instance_id'] !== $this->instance_id
)
187 if (!$this->instance_id
) {
188 $res = $this->db
->query('SELECT MAX(instance_id) AS max_instance_id FROM ninja_widgets WHERE name='.$this->db
->escape($this->name
).' AND page='.$this->db
->escape($this->page
).' AND username='.$this->db
->escape($this->username
));
189 $res = $res->current();
190 if (isset($res->max_instance_id
))
191 $this->instance_id
= $res->max_instance_id +
1;
193 $this->instance_id
= self
::FIRST_INSTANCE_ID
;
195 $sql = 'INSERT INTO ninja_widgets (username, page, name, friendly_name, setting, instance_id) VALUES (%s, %s, %s, %s, %s, %s)';
198 $sql = 'UPDATE ninja_widgets SET username=%s, page=%s, name=%s, friendly_name=%s, setting=%s, instance_id=%s WHERE id='.$this->db
->escape($this->id
);
201 $this->db
->query(sprintf($sql,
202 $this->db
->escape($this->username
),
203 $this->db
->escape($this->page
),
204 $this->db
->escape(self
::clean_widget_name($this->name
)),
205 $this->db
->escape($this->friendly_name
),
206 $this->db
->escape(serialize($this->setting
)),
207 $this->db
->escape($this->instance_id
)));
209 if (!isset($this->id
)) {
210 $res = $this->db
->query(sprintf('SELECT id FROM ninja_widgets WHERE username%s AND page%s AND name=%s AND instance_id=%s',
211 $this->username ?
'='.$this->db
->escape($this->username
) : ' IS NULL',
212 $this->page ?
'='.$this->db
->escape($this->page
) : ' IS NULL',
213 $this->db
->escape(self
::clean_widget_name($this->name
)),
214 $this->db
->escape($this->instance_id
)));
215 $this->id
= $res->current()->id
;
218 foreach ($this->db_row
as $key => $_) {
219 $this->db_row
[$key] = $this->$key;
225 * Given an array of new settings, update the local settings with those.
226 * Any preexisting settings not in $new_setting will be kept around.
227 * This does not write to database - see save()
229 * @param $new_setting Array of new settings to overwrite the old ones with
231 public function merge_settings($new_setting)
233 if (!is_array($this->setting
))
234 $this->setting
= array();
235 $this->setting
= array_merge($this->setting
, $new_setting);
239 * Add a new widget to ninja_widgets table
240 * @param $page The name of the page this should be displayed on - usually 'tac/index'
241 * @param $name The internal name of the widget
242 * @param $friendly_name The widget name that users should see
243 * @return false on error, true otherwise
245 public static function install($page, $name, $friendly_name)
247 if (empty($name) ||
empty($friendly_name)) {
251 if (Ninja_widget_Model
::get($page, $name) !== false) {
252 # widget already exists
255 $db = Database
::instance();
256 $sql = "INSERT INTO ninja_widgets(name, page, friendly_name) ".
257 'VALUES('.$db->escape($name).', '.$db->escape($page).', '.$db->escape($friendly_name).')';
259 # add the new widget to the widget_order string
264 * Remove any instance of a widget from the ninja_widgets stable
265 * Scary to expose to end users, as it does no authorization checks.
266 * @param $name The name of the widget to uninstall
268 public static function uninstall($name)
270 $db = Database
::instance();
271 $sql = 'DELETE FROM ninja_widgets WHERE name='.$db->escape($name);
273 $sql = "SELECT id, setting FROM ninja_settings WHERE type='widget_order'";
274 $result = $db->query($sql);
275 foreach ($result as $row) {
276 if (strpos($row->setting
, $name) === false)
278 $parsed_setting = self
::parse_widget_order($row->setting
);
280 foreach ($parsed_setting as $container => $widgets) {
281 foreach ($widgets as $idx => $id) {
282 if (strpos($id, 'widget-'.$name) === 0) {
283 unset($parsed_setting[$container][$idx]);
286 $widget_string[] = $container . '=' . implode(',', $widgets);
288 $db->query('UPDATE ninja_settings SET setting='.$db->escape(implode('|', $widget_string)).' WHERE id='.$row->id
);
294 * Create new instance of widget and save
295 * @return A widget object for the copy
297 public function copy()
299 $db = Database
::instance();
300 $res = $db->query('SELECT MAX(instance_id) AS max_instance_id FROM ninja_widgets WHERE name='.$db->escape($this->name
).' AND page='.$db->escape($this->page
).' AND username='.$db->escape($this->username
));
301 $res = $res->current();
302 $the_copy = self
::get($this->page
, $this->name
, $this->instance_id
);
303 unset($the_copy->id
);
304 if (isset($res->max_instance_id
))
305 $the_copy->instance_id
= $res->max_instance_id +
1;
307 $the_copy->instance_id
= self
::FIRST_INSTANCE_ID
;
309 $order = self
::fetch_widget_order($this->page
);
310 if (is_array($order)) {
311 foreach ($order as $container => $widgets) {
312 if (in_array('widget-'.$this->name
.'-'.$this->instance_id
, $widgets))
313 $order[$container][] = 'widget-'.$this->name
.'-'.$the_copy->instance_id
;
316 $order = array('unknown' => array('widget-'.$this->name
.'-'.$the_copy->instance_id
));
318 self
::set_widget_order($this->page
, $order);
323 * Deletes a copy of a widget.
325 public function delete()
329 $sql = 'DELETE FROM ninja_widgets WHERE id='.$this->db
->escape($this->id
);
330 $this->db
->query($sql);
335 * Clean the widget name received from easywidgets
337 private function clean_widget_name($widget)
339 return str_replace('#widget-', '', trim($widget));
343 * Update setting for all widgets on a page
345 public static function update_all_widgets($page=false, $value=false, $type='refresh_interval')
347 if (empty($page) ||
($value!=0 && empty($value)) ||
empty($type))
350 # fetch all available widgets for a page
351 $all_widgets = self
::fetch_all($page);
352 if ($all_widgets !== false) {
353 $new_setting = array($type => $value);
354 foreach ($all_widgets as $widget) {
355 if ($widget->setting
== false)
357 $widget->merge_settings($new_setting);
365 private static function parse_widget_order($setting)
367 $widget_order = false;
368 if (!empty($setting)) {
369 $widget_parts = explode('|', $setting);
370 if (!empty($widget_parts)) {
371 foreach ($widget_parts as $part) {
372 $parts = explode('=', $part);
373 if (is_array($parts) && !empty($parts)) {
374 $widget_sublist = explode(',', $parts[1]);
375 if (is_array($widget_sublist) && !empty($widget_sublist)) {
376 $widget_order[$parts[0]] = $widget_sublist;
382 return $widget_order;
386 * Parse the widget order for use on a page
388 public static function fetch_widget_order($page=false)
390 $data = Ninja_setting_Model
::fetch_page_setting('widget_order', $page);
391 if ($data === false ||
empty($data->setting
)) {
394 return self
::parse_widget_order($data->setting
);
398 * Given a structure like array(placeholder1 => array(widget1, widget2, widgetn), placeholder2 => array(...))
399 * serialize it and save it in the database.
401 public static function set_widget_order($page, $widget_order) {
403 unset($widget_order['unknown']);
404 foreach ($widget_order as $key => $ary) {
405 $res[] = "$key=".implode(',', $ary);
407 return Ninja_setting_Model
::save_page_setting('widget_order', $page, implode('|', $res));
411 * DANGER WILL ROBINSON!
412 * This makes a ton of assumptions, and should only be called after much
415 public static function rename_widget($old_name, $new_name)
417 $db = Database
::instance();
418 $sql = 'UPDATE ninja_widgets SET name='.$db->escape($new_name).' WHERE name='.$db->escape($old_name);
423 * Renames the friendly name of a widget. Friendly name is the name shown on widget title bar.
425 public static function rename_friendly_widget($old_name, $new_name)
427 $db = Database
::instance();
428 $sql = 'UPDATE ninja_widgets SET friendly_name='.$db->escape($new_name).' WHERE friendly_name='.$db->escape($old_name);