Browse Source

Add somewhat interesting package listings, fixes #90, fixes #252

Jordi Boggiano 12 years ago
parent
commit
9322dd24c2

+ 74 - 7
src/Packagist/WebBundle/Controller/WebController.php

@@ -26,6 +26,7 @@ use Packagist\WebBundle\Form\Model\SearchQuery;
 use Packagist\WebBundle\Package\Updater;
 use Packagist\WebBundle\Entity\Package;
 use Packagist\WebBundle\Entity\Version;
+use Packagist\WebBundle\Model\FixedAdapter;
 use Packagist\WebBundle\Form\Type\PackageType;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Response;
@@ -40,6 +41,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
 use Pagerfanta\Pagerfanta;
 use Pagerfanta\Adapter\DoctrineORMAdapter;
 use Pagerfanta\Adapter\SolariumAdapter;
+use Predis\Network\ConnectionException;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
@@ -56,10 +58,10 @@ class WebController extends Controller
     }
 
     /**
-     * @Template()
-     * @Route("/packages/", name="browse")
+     * @Template("PackagistWebBundle:Web:browse.html.twig")
+     * @Route("/packages/", name="allPackages")
      */
-    public function browseAction(Request $req)
+    public function allAction(Request $req)
     {
         $filters = array(
             'type' => $req->query->get('type'),
@@ -80,6 +82,71 @@ class WebController extends Controller
         return $data;
     }
 
+    /**
+     * @Template()
+     * @Route("/explore/", name="browse")
+     */
+    public function exploreAction(Request $req)
+    {
+        $pkgRepo = $this->getDoctrine()->getRepository('PackagistWebBundle:Package');
+        $verRepo = $this->getDoctrine()->getRepository('PackagistWebBundle:Version');
+        $newSubmitted = $pkgRepo->getQueryBuilderForNewestPackages()->setMaxResults(10)->getQuery()->getResult();
+        $newReleases = $verRepo->getLatestReleases(10);
+        $randomIds = $this->getDoctrine()->getConnection()->fetchAll('SELECT id FROM package ORDER BY RAND() LIMIT 10');
+        $random = $pkgRepo->createQueryBuilder('p')->where('p.id IN (:ids)')->setParameter('ids', $randomIds)->getQuery()->getResult();
+        try {
+            $popularIds = $this->get('snc_redis.default')->zrevrange('downloads:trending', 0, 9);
+            $popular = $pkgRepo->createQueryBuilder('p')->where('p.id IN (:ids)')->setParameter('ids', $popularIds)->getQuery()->getResult();
+            usort($popular, function ($a, $b) use ($popularIds) {
+                return array_search($a->getId(), $popularIds) > array_search($b->getId(), $popularIds) ? 1 : -1;
+            });
+        } catch (ConnectionException $e) {
+            $popular = array();
+        }
+
+        $data = array(
+            'newlySubmitted' => $newSubmitted,
+            'newlyReleased' => $newReleases,
+            'random' => $random,
+            'popular' => $popular,
+            'searchForm' => $this->createSearchForm()->createView(),
+        );
+
+        return $data;
+    }
+
+    /**
+     * @Template()
+     * @Route("/explore/popular", name="browse_popular")
+     */
+    public function popularAction(Request $req)
+    {
+        $redis = $this->get('snc_redis.default');
+        $popularIds = $redis->zrevrange(
+            'downloads:trending',
+            ($req->get('page', 1) - 1) * 15,
+            $req->get('page', 1) * 15 - 1
+        );
+        $popular = $this->getDoctrine()->getRepository('PackagistWebBundle:Package')
+            ->createQueryBuilder('p')->where('p.id IN (:ids)')->setParameter('ids', $popularIds)
+            ->getQuery()->getResult();
+        usort($popular, function ($a, $b) use ($popularIds) {
+            return array_search($a->getId(), $popularIds) > array_search($b->getId(), $popularIds) ? 1 : -1;
+        });
+
+        $packages = new Pagerfanta(new FixedAdapter($popular, $redis->zcard('downloads:trending')));
+        $packages->setMaxPerPage(15);
+        $packages->setCurrentPage($req->get('page', 1), false, true);
+
+        $data = array(
+            'packages' => $packages,
+            'searchForm' => $this->createSearchForm()->createView(),
+        );
+        $data['meta'] = $this->getPackagesMetadata($data['packages']);
+
+        return $data;
+    }
+
     /**
      * @Route("/packages/list.json", name="list", defaults={"_format"="json"})
      * @Method({"GET"})
@@ -383,7 +450,7 @@ class WebController extends Controller
             if ($this->getUser()) {
                 $data['is_favorite'] = $this->get('packagist.favorite_manager')->isMarked($this->getUser(), $package);
             }
-        } catch (\Exception $e) {
+        } catch (ConnectionException $e) {
             $data['downloads'] = array(
                 'total' => 'N/A',
                 'monthly' => 'N/A',
@@ -672,9 +739,9 @@ class WebController extends Controller
                 'labels' => array_keys($dlChartMonthly),
                 'values' => $redis->mget(array_values($dlChartMonthly))
             );
-        } catch (\Exception $e) {
+        } catch (ConnectionException $e) {
             $downloads = 'N/A';
-            $dlChart = null;
+            $dlChart = $dlChartMonthly = null;
         }
 
         return array(
@@ -736,7 +803,7 @@ class WebController extends Controller
                 /** @var $redis \Snc\RedisBundle\Client\Phpredis\Client */
                 $redis = $this->get('snc_redis.default');
                 $downloads = $redis->get('dl:'.$package->getId());
-            } catch (\Exception $e) {
+            } catch (ConnectionException $e) {
                 return;
             }
 

+ 12 - 0
src/Packagist/WebBundle/Entity/VersionRepository.php

@@ -96,4 +96,16 @@ class VersionRepository extends EntityRepository
 
         return $qb;
     }
+
+    public function getLatestReleases($count = 10)
+    {
+        $qb = $this->getEntityManager()->createQueryBuilder();
+        $qb->select('v')
+            ->from('Packagist\WebBundle\Entity\Version', 'v')
+            ->where('v.development = false')
+            ->orderBy('v.releasedAt', 'DESC')
+            ->setMaxResults(10);
+
+        return $qb->getQuery()->getResult();
+    }
 }

+ 46 - 0
src/Packagist/WebBundle/Model/FixedAdapter.php

@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Model;
+
+use Pagerfanta\Adapter\AdapterInterface;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class FixedAdapter implements AdapterInterface
+{
+    protected $data;
+    protected $count;
+
+    public function __construct($data, $count)
+    {
+        $this->data = $data;
+        $this->count = $count;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getNbResults()
+    {
+        return $this->count;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getSlice($offset, $length)
+    {
+        return $this->data;
+    }
+}

+ 5 - 0
src/Packagist/WebBundle/Resources/config/services.yml

@@ -5,6 +5,11 @@ services:
         tags:
             - { name: twig.extension }
 
+    twig.extension.text:
+        class: Twig_Extensions_Extension_Text
+        tags:
+            - { name: twig.extension }
+
     packagist.package_dumper:
         class: Packagist\WebBundle\Package\Dumper
         arguments: [ @doctrine, @filesystem, @router, %kernel.root_dir%/../web/, %kernel.cache_dir% ]

+ 15 - 0
src/Packagist/WebBundle/Resources/public/css/main.css

@@ -404,6 +404,21 @@ form ul {
   margin: 10px 0;
 }
 
+/* Explore */
+.packages-short {
+  width: 50%;
+  float: left;
+  height: 415px;
+}
+.packages-short li a {
+  display: block;
+}
+.packages-short ul {
+  list-style: none;
+  margin: 0;
+}
+
+
 /* Search */
 #search_query_query {
   width: 890px;

+ 44 - 0
src/Packagist/WebBundle/Resources/views/Web/explore.html.twig

@@ -0,0 +1,44 @@
+{% extends "PackagistWebBundle::layout.html.twig" %}
+
+{% import "PackagistWebBundle::macros.html.twig" as macros %}
+
+{% block content %}
+    <div class="box clearfix">
+        {% block content_title %}<h1>Packages</h1>{% endblock %}
+        {% block lists %}
+            <div class="packages-short">
+                <h2>New Releases <a href="{{ url('feed_releases', {_format: 'rss'}) }}">RSS</a></h2>
+                <ul>
+                    {% for version in newlyReleased %}
+                        <li><a href="{{ path('view_package', {name: version.name}) }}">{{ version.name }} {{ version.version }}</a> {{ version.description|truncate(40) }}</li>
+                    {% endfor %}
+                </ul>
+            </div>
+            <div class="packages-short">
+                <h2>New Packages <a href="{{ url('feed_packages', {_format: 'rss'}) }}">RSS</a></h2>
+                <ul>
+                    {% for pkg in newlySubmitted %}
+                        <li><a href="{{ path('view_package', {name: pkg.name}) }}">{{ pkg.name }}</a> {{ pkg.description|truncate(40) }}</li>
+                    {% endfor %}
+                </ul>
+            </div>
+            <div class="packages-short">
+                <h2>Popular Packages</h2>
+                <ul>
+                    {% for pkg in popular %}
+                        <li><a href="{{ path('view_package', {name: pkg.name}) }}">{{ pkg.name }}</a> {{ pkg.description|truncate(40) }}</li>
+                    {% endfor %}
+                    <li><a href="{{ path('browse_popular') }}">See more...</a></li>
+                </ul>
+            </div>
+            <div class="packages-short">
+                <h2>Random Packages</h2>
+                <ul>
+                    {% for pkg in random %}
+                        <li><a href="{{ path('view_package', {name: pkg.name}) }}">{{ pkg.name }}</a> {{ pkg.description|truncate(40) }}</li>
+                    {% endfor %}
+                </ul>
+            </div>
+        {% endblock %}
+    </div>
+{% endblock %}

+ 3 - 0
src/Packagist/WebBundle/Resources/views/Web/popular.html.twig

@@ -0,0 +1,3 @@
+{% extends "PackagistWebBundle:Web:list.html.twig" %}
+
+{% block content_title %}<h1>Popular Packages</h1>{% endblock %}

+ 1 - 1
src/Packagist/WebBundle/Resources/views/Web/stats.html.twig

@@ -15,8 +15,8 @@
         {% if downloadsChartMonthly %}
             <h2>Packages installed per month</h2>
             <p><img src="http://chart.apis.google.com/chart?chxr=0,0,{{ maxMonthlyDownloads }}&amp;chxl=1:|{{ downloadsChartMonthly.labels|join('|') }}&amp;chxt=y,x&amp;chs=900x250&amp;chds=0,{{ maxMonthlyDownloads }},0,{{ maxMonthlyDownloads }}&amp;cht=lc&amp;chco=0000FF,FF9900&amp;chd=t:{{ downloadsChartMonthly.values|join(',') }}&amp;chdl=Installs&amp;chls=2|2" /></p>
+            <p>The last data point is for the current month and shows partial data.</p>
         {% endif %}
-        <p>The last data point is for the current month and shows partial data.</p>
 
         <h2>Totals</h2>
         <p>{{ packages|number_format(0, '.', " ") }} packages registered</p>