1<?php
2/**
3 * lastfm functions for the lastfm plugin
4 *
5 * @license:    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author:     Michael Klier <chi@chimeric.de>
7 */
8
9if(!defined('DW_LF')) define('DW_LF',"\n");
10require_once(DOKU_INC.'inc/html.php');
11
12/**
13 * outputs the requested lastfm chart
14 *
15 * @author Michael Klier <chi@chimeric.de>
16 */
17function lastfm_xhtml($user,$chart,$limit,$dformat,$utc_offset,$cols,$imgonly) {
18    global $lang;
19
20    $data = array();
21    $xml  = lastfm_get_xml($user,$chart);
22    $data = ($chart != 'profile') ? @array_pop(lastfm_xml2array($xml)) : lastfm_xml2array($xml);
23
24    // do we have any data?
25    if(!is_array($data)) {
26        print $lang['nothingfound'];
27        return;
28    };
29
30    // apply limit
31    if($chart != 'profile') $data = array_slice($data,0,$limit);
32
33    print '<table class="plugin_lastfm_chart plugin_lastfm_' . $chart . '">' . DW_LF;
34
35    switch($chart) {
36
37        case 'topartists':
38            foreach($data as $rcd) {
39                print '<tr>' . DW_LF;
40                print '  <td class="plugin_lastfm_rank">' . $rcd['rank'] . '.</td>' . DW_LF;
41                print '  <td class="plugin_lastfm_artist">' . DW_LF;
42                print '    <a href="' . $rcd['url'] . '" title="' . $rcd['name'] . '">' . $rcd['name'] . '</a>' . DW_LF;
43                print '  </td>' . DW_LF;
44                print '  <td>' . DW_LF;
45                print '    <div class="plugin_lastfm_playcount"style="width:';
46                print round($rcd['playcount'] / 2);
47                print 'px;">' . $rcd['playcount'] . '</div>' . DW_LF;
48                print '  </td>' . DW_LF;
49                print '</tr>' . DW_LF;
50            }
51            break;
52
53        case 'topalbums':
54            if(!$imgonly) {
55                foreach($data as $rcd) {
56                        print '<tr>' . DW_LF;
57                        print '  <td class="plugin_lastfm_rank">' . $rcd['rank'] . '.</td>' . DW_LF;
58                        print '  <td>' . DW_LF;
59                        print '    <a href="' . $rcd['url'] . '" title="' . $rcd['artist'] . ' - ' . $rcd['name'] . '">' . DW_LF;
60                        print '      <img src="' . $rcd['image']['small'] . '" height="45px" width="45px" alt="' . $rcd['name'] . '" />' . DW_LF;
61                        print '    </a>' . DW_LF;
62                        print '  </td>' . DW_LF;
63                        print '  <td class="plugin_lastfm_artist"><a href="' . $rcd['url'] .'" title="' . $rcd['artist'] . '">' . $rcd['artist'] . ' [' . $rcd['name'] . ']</a></td>' . DW_LF;
64                        print '  <td class="plugin_lastfm_count">' . $rcd['playcount'] . '</td>' . DW_LF;
65                        print '</tr>' . DW_LF;
66                }
67            } else {
68                $num = count($data);
69                $col = 1;
70
71                for($i=0;$i<$num;$i++) {
72                    if($col == 1) print '<tr>' . DW_LF;
73
74                    print '  <td>' . DW_LF;
75                    print '    <a href="' . $data[$i]['url'] . '" title="' . $data[$i]['artist'] . ' - ' . $data[$i]['name'] . '">' . DW_LF;
76                    print '      <img src="' . $data[$i]['image']['small'] . '" height="45px" width="45px" alt="' . $data[$i]['name'] . '" />' . DW_LF;
77                    print '    </a>' . DW_LF;
78                    print '  </td>' . DW_LF;
79
80                    if($col == $cols) {
81                        print '</tr>' . DW_LF;
82                        $col = 1;
83                    } else {
84                        $col++;
85                    }
86                }
87            }
88            break;
89
90        case 'toptracks':
91            foreach($data as $rcd) {
92                print '<tr>' . DW_LF;
93                print '  <td class="plugin_lastfm_rank">' . $rcd['rank'] . '.</td>' . DW_LF;
94                print '  <td class="plugin_lastfm_track"><a href="' . $rcd['url'] . '" title="' . $rcd['artist'] . '">' . $rcd['name'] . '</a></td>' . DW_LF;
95                print '  <td class="plugin_lastfm_count">' . $rcd['playcount'] . '</td>' . DW_LF;
96                print '</tr>' . DW_LF;
97            }
98            break;
99
100        case 'tags':
101            foreach($data as $rcd) {
102                print '<tr>' . DW_LF;
103                print '  <td class="plugin_lastfm_count">' . $rcd['count'] . '</td>' . DW_LF;
104                print '  <td class="plugin_lastfm_tag"><a href="' . $rcd['url'] . '" title="' . $rcd['name'] . '">' . $rcd['name'] . '</a></td>' . DW_LF;
105                print '</tr>' . DW_LF;
106            }
107            break;
108
109        case 'friends':
110            $num = count($data);
111            $col = 1;
112
113            for($i=0;$i<$num;$i++) {
114                if($col == 1) print '<tr>' . DW_LF;
115
116                print '  <td class="plugin_lastfm_friend">' . DW_LF;
117                print '    <a href="' . $data[$i]['url'] . '" title="' . $data[$i]['attributes']['username'] . '">' . DW_LF;
118                print '     <img src="' . $data[$i]['image'] . '" alt="' . $data[$i]['attributes']['username'] . '" width="45px" height="45px" />' . DW_LF;
119                print '    </a>' . DW_LF;
120                print '  </td>' . DW_LF;
121
122                if($col == $cols) {
123                    print '</tr>' . DW_LF;
124                    $col = 1;
125                } else {
126                    $col++;
127                }
128            }
129            break;
130
131        case 'neighbours':
132            $num = count($data);
133            $col = 1;
134
135            for($i=0;$i<$num;$i++) {
136                if($col == 1) print '<tr>' . DW_LF;
137
138                print '  <td class="plugin_lastfm_friend">' . DW_LF;
139                print '    <a href="' . $data[$i]['url'] . '" title="' . $data[$i]['attributes']['username'] . '">' . DW_LF;
140                print '     <img src="' . $data[$i]['image'] . '" alt="' . $data[$i]['attributes']['username'] . ' ' . $data[$i]['match'] . '%" width="45px" height="45px" />' . DW_LF;
141                print '    </a>' . DW_LF;
142                print '  </td>' . DW_LF;
143
144                if($col == $cols) {
145                    print '</tr>' . DW_LF;
146                    $col = 1;
147                } else {
148                    $col++;
149                }
150            }
151            break;
152
153        case 'recenttracks':
154            foreach($data as $rcd) {
155                print '<tr>' . DW_LF;
156                print '  <td class="plugin_lastfm_artist">' . DW_LF;
157                print '    <a href="' . $rcd['url'] . '" title="' . $rcd['artist'] . ' &middot; ' . $rcd['name'] . '">' . $rcd['artist'] . ' &middot; ' . $rcd['name'] . '</a>' . DW_LF;
158                print '  </td>' . DW_LF;
159                print '  <td class="plugin_lastfm_date">' . lastfm_cvdate($rcd['date'],$dformat,$utc_offset) . '</td>' . DW_LF;
160                print '</tr>' . DW_LF;
161            }
162            break;
163
164        case 'weeklyartistchart':
165            foreach($data as $rcd) {
166                print '<tr>' . DW_LF;
167                print '  <td class="plugin_lastfm_rank">' . $rcd['chartposition'] . '.</td>' . DW_LF;
168                print '  <td class="plugin_lastfm_artist">' . DW_LF;
169                print '    <a href="' . $rcd['url'] . '" title="' . $rcd['name'] . '">' . $rcd['name'] . '</a>' . DW_LF;
170                print '  </td>' . DW_LF;
171                print '  <td class="plugin_lastfm_playcount">' . $rcd['playcount'] . '</td>' . DW_LF;
172                print '</tr>' . DW_LF;
173            }
174            break;
175
176        case 'weeklyalbumchart':
177            foreach($data as $rcd) {
178                // FIXME mbid field
179                print '<tr>' . DW_LF;
180                print '  <td class="plugin_lastfm_rank">' . $rcd['chartposition'] . '.</td>' . DW_LF;
181                print '  <td class="plugin_lastfm_artist">' . DW_LF;
182                print '    <a href="' . $rcd['url'] . '" title="' . $rcd['artist'] . ' &middot; ' . $rcd['name'] . '">' . $rcd['artist'] . ' &middot; ' . $rcd['name'] . '</a>' . DW_LF;
183                print '  </td>' . DW_LF;
184                print '  <td class="plugin_lastfm_playcount">' . $rcd['playcount'] . '</td>' . DW_LF;
185                print '</tr>' . DW_LF;
186            }
187            break;
188
189        case 'weeklytrackchart':
190            foreach($data as $rcd) {
191                print '<tr>' . DW_LF;
192                print '  <td class="plugin_lastfm_rank">' . $rcd['chartposition'] . '.</td>' . DW_LF;
193                print '  <td class="plugin_lastfm_artist">' . DW_LF;
194                print '    <a href="' . $rcd['url'] . '" title="' . $rcd['artist'] . ' &middot; ' . $rcd['name'] . '">' . $rcd['artist'] . ' &middot; ' . $rcd['name'] . '</a>' . DW_LF;
195                print '  </td>' . DW_LF;
196                print '  <td class="plugin_lastfm_playcount">' . $rcd['playcount'] . '</td>' . DW_LF;
197                print '</tr>' . DW_LF;
198            }
199            break;
200
201        case 'profile':
202            print '<a href="' . $data['url'] . '" title="' . $user . '">' . DW_LF;
203            print '  <img src="' . $data['avatar'] . '" alt="' . $user . '" />' . DW_LF;
204            print '</a>' . DW_LF;
205            break;
206    }
207
208    print '</table>' . DW_LF;
209}
210
211/**
212 * gets the xml file from the lastfm page
213 *
214 * @author Michael Klier <chi@chimeric.de>
215 */
216function lastfm_get_xml($user,$chart) {
217    $xml  = '';
218    $http = new DokuHTTPClient();
219    $url  = 'http://ws.audioscrobbler.com/1.0/user/';
220    $url .= $user . '/' . $chart . '.xml';
221
222    $xml = $http->get($url);
223    if($http->status == 200)
224        return $xml;
225}
226
227/**
228 * converst the date provided by the lastfm service
229 *
230 * @author Michael Klier <chi@chimeric.de>
231 */
232function lastfm_cvdate($date,$dformat,$utc_offset) {
233    list($day,$month,$year,$time) = explode(' ',$date);
234    list($hour,$min) = explode(':',$time);
235    $year  = substr($year,0,-1); // remove trailing comma
236    $hour  = $hour + $utc_offset;
237    return date($dformat,strtotime($day." ".$month." ".$year. " ".$hour.":".$min));
238}
239
240/**
241 * wrapper function around _xmlToArray()
242 */
243function lastfm_xml2array($xml) {
244    return _xmlToArray($xml);
245}
246
247/**
248 * This static method converts an xml file to an associative array
249 * duplicating the xml file structure.
250 *
251 * @param    $fileName. String. The name of the xml file to convert.
252 *             This method returns an Error object if this file does not
253 *             exist or is invalid.
254 * @param    $includeTopTag. booleal. Whether or not the topmost xml tag
255 *             should be included in the array. The default value for this is false.
256 * @param    $lowerCaseTags. boolean. Whether or not tags should be
257 *            set to lower case. Default value for this parameter is true.
258 * @access    public static
259 * @return    Associative Array
260 * @author    Jason Read <jason@ace.us.com>
261 * @author    Michael Klier <chi@chimeric.de>
262 */
263function & _xmlToArray($xml_raw, $includeTopTag = false, $lowerCaseTags = true)
264{
265    $p = xml_parser_create();
266    xml_parse_into_struct($p,$xml_raw,$vals,$index);
267    xml_parser_free($p);
268    $xml = array();
269    $levels = array();
270    $multipleData = array();
271    $prevTag = "";
272    $currTag = "";
273    $topTag = false;
274    foreach ($vals as $val)
275    {
276        // Open tag
277        if ($val["type"] == "open")
278        {
279            if (!_xmlFileToArrayOpen($topTag, $includeTopTag, $val, $lowerCaseTags,
280                                           $levels, $prevTag, $multipleData, $xml))
281            {
282                continue;
283            }
284        }
285        // Close tag
286        else if ($val["type"] == "close")
287        {
288            if (!_xmlFileToArrayClose($topTag, $includeTopTag, $val, $lowerCaseTags,
289                                            $levels, $prevTag, $multipleData, $xml))
290            {
291                continue;
292            }
293        }
294        // Data tag
295        else if ($val["type"] == "complete" && isset($val["value"]))
296        {
297            $loc =& $xml;
298            foreach ($levels as $level)
299            {
300                $temp =& $loc[str_replace(":arr#", "", $level)];
301                $loc =& $temp;
302            }
303            $tag = $val["tag"];
304            if ($lowerCaseTags)
305            {
306                $tag = strtolower($val["tag"]);
307            }
308            $loc[$tag] = str_replace("\\n", "\n", $val["value"]);
309        }
310        // Tag without data
311        else if ($val["type"] == "complete")
312        {
313            _xmlFileToArrayOpen($topTag, $includeTopTag, $val, $lowerCaseTags,
314                                      $levels, $prevTag, $multipleData, $xml);
315            _xmlFileToArrayClose($topTag, $includeTopTag, $val, $lowerCaseTags,
316                                      $levels, $prevTag, $multipleData, $xml);
317        }
318    }
319    return $xml;
320}
321
322/**
323 * Private support function for xmlFileToArray. Handles an xml OPEN tag.
324 *
325 * @param    $topTag. String. xmlFileToArray topTag variable
326 * @param    $includeTopTag. boolean. xmlFileToArray includeTopTag variable
327 * @param    $val. String[]. xmlFileToArray val variable
328 * @param    $currTag. String. xmlFileToArray currTag variable
329 * @param    $lowerCaseTags. boolean. xmlFileToArray lowerCaseTags variable
330 * @param    $levels. String[]. xmlFileToArray levels variable
331 * @param    $prevTag. String. xmlFileToArray prevTag variable
332 * @param    $multipleData. boolean. xmlFileToArray multipleData variable
333 * @param    $xml. String[]. xmlFileToArray xml variable
334 * @access    private static
335 * @return    boolean
336 * @author    Jason Read <jason@ace.us.com>
337 */
338function _xmlFileToArrayOpen(& $topTag, & $includeTopTag, & $val, & $lowerCaseTags,
339                             & $levels, & $prevTag, & $multipleData, & $xml)
340{
341    // don't include top tag
342    if (!$topTag && !$includeTopTag)
343    {
344        $topTag = $val["tag"];
345        return false;
346    }
347    $currTag = $val["tag"];
348    if ($lowerCaseTags)
349    {
350        $currTag = strtolower($val["tag"]);
351    }
352    $levels[] = $currTag;
353
354    // Multiple items w/ same name. Convert to array.
355    if ($prevTag === $currTag)
356    {
357        if (!array_key_exists($currTag, $multipleData) ||
358            !$multipleData[$currTag]["multiple"])
359        {
360            $loc =& $xml;
361            foreach ($levels as $level)
362            {
363                $temp =& $loc[$level];
364                $loc =& $temp;
365            }
366            $loc = array($loc);
367            $multipleData[$currTag]["multiple"] = true;
368            $multipleData[$currTag]["multiple_count"] = 0;
369        }
370        $multipleData[$currTag]["popped"] = false;
371        $levels[] = ":arr#" . ++$multipleData[$currTag]["multiple_count"];
372    }
373    else
374    {
375        $multipleData[$currTag]["multiple"] = false;
376    }
377
378    // Add attributes array
379    if (array_key_exists("attributes", $val))
380    {
381        $loc =& $xml;
382        foreach ($levels as $level)
383        {
384            $temp =& $loc[str_replace(":arr#", "", $level)];
385            $loc =& $temp;
386        }
387        $keys = array_keys($val["attributes"]);
388        foreach ($keys as $key)
389        {
390            $tag = $key;
391            if ($lowerCaseTags)
392            {
393                $tag = strtolower($tag);
394            }
395            $loc["attributes"][$tag] = & $val["attributes"][$key];
396        }
397    }
398    return true;
399}
400
401/**
402 * Private support function for xmlFileToArray. Handles an xml OPEN tag.
403 *
404 * @param    $topTag. String. xmlFileToArray topTag variable
405 * @param    $includeTopTag. boolean. xmlFileToArray includeTopTag variable
406 * @param    $val. String[]. xmlFileToArray val variable
407 * @param    $currTag. String. xmlFileToArray currTag variable
408 * @param    $lowerCaseTags. boolean. xmlFileToArray lowerCaseTags variable
409 * @param    $levels. String[]. xmlFileToArray levels variable
410 * @param    $prevTag. String. xmlFileToArray prevTag variable
411 * @param    $multipleData. boolean. xmlFileToArray multipleData variable
412 * @param    $xml. String[]. xmlFileToArray xml variable
413 * @access    private static
414 * @return    boolean
415 * @author    Jason Read <jason@ace.us.com>
416 */
417function _xmlFileToArrayClose(& $topTag, & $includeTopTag, & $val, & $lowerCaseTags,
418                              & $levels, & $prevTag, & $multipleData, & $xml)
419{
420    // don't include top tag
421    if ($topTag && !$includeTopTag && $val["tag"] == $topTag)
422    {
423        return false;
424    }
425    if ($multipleData[$currTag]["multiple"])
426    {
427        $tkeys = array_reverse(array_keys($multipleData));
428        foreach ($tkeys as $tkey)
429        {
430            if ($multipleData[$tkey]["multiple"] && !$multipleData[$tkey]["popped"])
431            {
432                array_pop($levels);
433                $multipleData[$tkey]["popped"] = true;
434                break;
435            }
436            else if (!$multipleData[$tkey]["multiple"])
437            {
438                break;
439            }
440        }
441    }
442    $prevTag = array_pop($levels);
443    if (strpos($prevTag, "arr#"))
444    {
445        $prevTag = array_pop($levels);
446    }
447    return true;
448}
449
450// vim:ts=4:sw=4:et:enc=utf8:
451