Merge pull request #1386 from wallabag/v2-refactor

WIP – Fixing things around 💨
This commit is contained in:
Nicolas Lœuillet 2015-08-20 21:51:02 +02:00
commit 109d67dbb1
16 changed files with 190 additions and 298 deletions

View file

@ -51,6 +51,7 @@ twig:
form: form:
resources: resources:
- LexikFormFilterBundle:Form:form_div_layout.html.twig - LexikFormFilterBundle:Form:form_div_layout.html.twig
# Assetic Configuration # Assetic Configuration
assetic: assetic:
debug: "%kernel.debug%" debug: "%kernel.debug%"

View file

@ -19,6 +19,7 @@
}, },
{ {
"name": "Jérémy Benoist", "name": "Jérémy Benoist",
"homepage": "http://www.j0k3r.net",
"role": "Developer" "role": "Developer"
} }
], ],

View file

@ -170,6 +170,31 @@ class WallabagRestControllerTest extends WebTestCase
$client = $this->createClient(); $client = $this->createClient();
$headers = $this->generateHeaders('admin', 'mypassword'); $headers = $this->generateHeaders('admin', 'mypassword');
$client->request('GET', '/api/entries', array('star' => 1, 'sort' => 'updated'), array(), $headers);
$this->assertEquals(200, $client->getResponse()->getStatusCode());
$content = json_decode($client->getResponse()->getContent(), true);
$this->assertGreaterThanOrEqual(1, count($content));
$this->assertNotEmpty($content['_embedded']['items']);
$this->assertGreaterThanOrEqual(1, $content['total']);
$this->assertEquals(1, $content['page']);
$this->assertGreaterThanOrEqual(1, $content['pages']);
$this->assertTrue(
$client->getResponse()->headers->contains(
'Content-Type',
'application/json'
)
);
}
public function testGetArchiveEntries()
{
$client = $this->createClient();
$headers = $this->generateHeaders('admin', 'mypassword');
$client->request('GET', '/api/entries', array('archive' => 1), array(), $headers); $client->request('GET', '/api/entries', array('archive' => 1), array(), $headers);
$this->assertEquals(200, $client->getResponse()->getStatusCode()); $this->assertEquals(200, $client->getResponse()->getStatusCode());

View file

@ -113,34 +113,7 @@ class EntryController extends Controller
*/ */
public function showUnreadAction(Request $request, $page) public function showUnreadAction(Request $request, $page)
{ {
$form = $this->get('form.factory')->create(new EntryFilterType()); return $this->showEntries('unread', $request, $page);
$filterBuilder = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findUnreadByUser($this->getUser()->getId());
if ($request->query->has($form->getName())) {
// manually bind values from the request
$form->submit($request->query->get($form->getName()));
// build the query from the given form object
$this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $filterBuilder);
}
$pagerAdapter = new DoctrineORMAdapter($filterBuilder->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$entries->setMaxPerPage($this->getUser()->getConfig()->getItemsPerPage());
$entries->setCurrentPage($page);
return $this->render(
'WallabagCoreBundle:Entry:entries.html.twig',
array(
'form' => $form->createView(),
'entries' => $entries,
'currentPage' => $page,
)
);
} }
/** /**
@ -155,34 +128,7 @@ class EntryController extends Controller
*/ */
public function showArchiveAction(Request $request, $page) public function showArchiveAction(Request $request, $page)
{ {
$form = $this->get('form.factory')->create(new EntryFilterType()); return $this->showEntries('archive', $request, $page);
$filterBuilder = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findArchiveByUser($this->getUser()->getId());
if ($request->query->has($form->getName())) {
// manually bind values from the request
$form->submit($request->query->get($form->getName()));
// build the query from the given form object
$this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $filterBuilder);
}
$pagerAdapter = new DoctrineORMAdapter($filterBuilder->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$entries->setMaxPerPage($this->getUser()->getConfig()->getItemsPerPage());
$entries->setCurrentPage($page);
return $this->render(
'WallabagCoreBundle:Entry:entries.html.twig',
array(
'form' => $form->createView(),
'entries' => $entries,
'currentPage' => $page,
)
);
} }
/** /**
@ -197,11 +143,64 @@ class EntryController extends Controller
*/ */
public function showStarredAction(Request $request, $page) public function showStarredAction(Request $request, $page)
{ {
return $this->showEntries('starred', $request, $page);
}
/**
* Global method to retrieve entries depending on the given type
* It returns the response to be send.
*
* @param string $type Entries type: unread, starred or archive
* @param Request $request
* @param int $page
*
* @return \Symfony\Component\HttpFoundation\Response
*/
private function showEntries($type, Request $request, $page)
{
$repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
switch ($type) {
case 'starred':
$qb = $repository->getBuilderForStarredByUser($this->getUser()->getId());
break;
case 'archive':
$qb = $repository->getBuilderForArchiveByUser($this->getUser()->getId());
break;
case 'unread':
$qb = $repository->getBuilderForUnreadByUser($this->getUser()->getId());
break;
default:
throw new \InvalidArgumentException(sprintf('Type "%s" is not implemented.', $type));
}
$form = $this->get('form.factory')->create(new EntryFilterType()); $form = $this->get('form.factory')->create(new EntryFilterType());
$filterBuilder = $this->getDoctrine() if ($request->query->has($form->getName())) {
->getRepository('WallabagCoreBundle:Entry') // manually bind values from the request
->findStarredByUser($this->getUser()->getId()); $form->submit($request->query->get($form->getName()));
// build the query from the given form object
$this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $qb);
}
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$entries->setMaxPerPage($this->getUser()->getConfig()->getItemsPerPage());
$entries->setCurrentPage($page);
return $this->render(
'WallabagCoreBundle:Entry:entries.html.twig',
array(
'form' => $form->createView(),
'entries' => $entries,
'currentPage' => $page,
)
);
if ($request->query->has($form->getName())) { if ($request->query->has($form->getName())) {
// manually bind values from the request // manually bind values from the request

View file

@ -22,22 +22,7 @@ class RssController extends Controller
*/ */
public function showUnreadAction(User $user) public function showUnreadAction(User $user)
{ {
$qb = $this->getDoctrine() return $this->showEntries('unread', $user);
->getRepository('WallabagCoreBundle:Entry')
->findUnreadByUser(
$user->getId()
);
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$perPage = $user->getConfig()->getRssLimit() ?: $this->container->getParameter('rss_limit');
$entries->setMaxPerPage($perPage);
return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array(
'type' => 'unread',
'entries' => $entries,
));
} }
/** /**
@ -50,22 +35,7 @@ class RssController extends Controller
*/ */
public function showArchiveAction(User $user) public function showArchiveAction(User $user)
{ {
$qb = $this->getDoctrine() return $this->showEntries('archive', $user);
->getRepository('WallabagCoreBundle:Entry')
->findArchiveByUser(
$user->getId()
);
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery());
$entries = new Pagerfanta($pagerAdapter);
$perPage = $user->getConfig()->getRssLimit() ?: $this->container->getParameter('rss_limit');
$entries->setMaxPerPage($perPage);
return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array(
'type' => 'archive',
'entries' => $entries,
));
} }
/** /**
@ -78,11 +48,38 @@ class RssController extends Controller
*/ */
public function showStarredAction(User $user) public function showStarredAction(User $user)
{ {
$qb = $this->getDoctrine() return $this->showEntries('starred', $user);
->getRepository('WallabagCoreBundle:Entry') }
->findStarredByUser(
$user->getId() /**
); * Global method to retrieve entries depending on the given type
* It returns the response to be send.
*
* @param string $type Entries type: unread, starred or archive
* @param User $user
*
* @return \Symfony\Component\HttpFoundation\Response
*/
private function showEntries($type, User $user)
{
$repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
switch ($type) {
case 'starred':
$qb = $repository->getBuilderForStarredByUser($user->getId());
break;
case 'archive':
$qb = $repository->getBuilderForArchiveByUser($user->getId());
break;
case 'unread':
$qb = $repository->getBuilderForUnreadByUser($user->getId());
break;
default:
throw new \InvalidArgumentException(sprintf('Type "%s" is not implemented.', $type));
}
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery()); $pagerAdapter = new DoctrineORMAdapter($qb->getQuery());
$entries = new Pagerfanta($pagerAdapter); $entries = new Pagerfanta($pagerAdapter);
@ -91,7 +88,7 @@ class RssController extends Controller
$entries->setMaxPerPage($perPage); $entries->setMaxPerPage($perPage);
return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array( return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array(
'type' => 'starred', 'type' => $type,
'entries' => $entries, 'entries' => $entries,
)); ));
} }

View file

@ -28,12 +28,4 @@ class StaticController extends Controller
array() array()
); );
} }
/**
* @Route("/", name="homepage")
*/
public function apiAction()
{
return $this->redirect($this->generateUrl('nelmio_api_doc_index'));
}
} }

View file

@ -7,7 +7,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
use Hateoas\Configuration\Annotation as Hateoas; use Hateoas\Configuration\Annotation as Hateoas;
use JMS\Serializer\Annotation\XmlRoot; use JMS\Serializer\Annotation\XmlRoot;
use Wallabag\CoreBundle\Helper\Tools; use Wallabag\CoreBundle\Tools\Utils;
/** /**
* Entry. * Entry.
@ -265,7 +265,7 @@ class Entry
public function setContent($content) public function setContent($content)
{ {
$this->content = $content; $this->content = $content;
$this->readingTime = Tools::getReadingTime($content); $this->readingTime = Utils::getReadingTime($content);
$this->domainName = parse_url($this->url, PHP_URL_HOST); $this->domainName = parse_url($this->url, PHP_URL_HOST);
return $this; return $this;

View file

@ -1,7 +0,0 @@
<?php
namespace Wallabag\CoreBundle\Helper;
class Entry
{
}

View file

@ -1,133 +0,0 @@
<?php
namespace Wallabag\CoreBundle\Helper;
final class Tools
{
/**
* Download a file (typically, for downloading pictures on web server).
*
* @param $url
*
* @return bool|mixed|string
*/
public static function getFile($url)
{
$timeout = 15;
$useragent = 'Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0';
if (in_array('curl', get_loaded_extensions())) {
# Fetch feed from URL
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
if (!ini_get('open_basedir') && !ini_get('safe_mode')) {
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, false);
# for ssl, do not verified certificate
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_AUTOREFERER, true);
# FeedBurner requires a proper USER-AGENT...
curl_setopt($curl, CURL_HTTP_VERSION_1_1, true);
curl_setopt($curl, CURLOPT_ENCODING, 'gzip, deflate');
curl_setopt($curl, CURLOPT_USERAGENT, $useragent);
$data = curl_exec($curl);
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$httpcodeOK = isset($httpcode) and ($httpcode == 200 or $httpcode == 301);
curl_close($curl);
} else {
# create http context and add timeout and user-agent
$context = stream_context_create(
array(
'http' => array(
'timeout' => $timeout,
'header' => 'User-Agent: '.$useragent,
'follow_location' => true,
),
'ssl' => array(
'verify_peer' => false,
'allow_self_signed' => true,
),
)
);
# only download page lesser than 4MB
$data = @file_get_contents($url, false, $context, -1, 4000000);
if (isset($http_response_header) and isset($http_response_header[0])) {
$httpcodeOK = isset($http_response_header) and isset($http_response_header[0]) and ((strpos($http_response_header[0], '200 OK') !== false) or (strpos($http_response_header[0], '301 Moved Permanently') !== false));
}
}
# if response is not empty and response is OK
if (isset($data) and isset($httpcodeOK) and $httpcodeOK) {
# take charset of page and get it
preg_match('#<meta .*charset=.*>#Usi', $data, $meta);
# if meta tag is found
if (!empty($meta[0])) {
preg_match('#charset="?(.*)"#si', $meta[0], $encoding);
# if charset is found set it otherwise, set it to utf-8
$html_charset = (!empty($encoding[1])) ? strtolower($encoding[1]) : 'utf-8';
if (empty($encoding[1])) {
$encoding[1] = 'utf-8';
}
} else {
$html_charset = 'utf-8';
$encoding[1] = '';
}
# replace charset of url to charset of page
$data = str_replace('charset='.$encoding[1], 'charset='.$html_charset, $data);
return $data;
} else {
return false;
}
}
/**
* Encode a URL by using a salt.
*
* @param $string
*
* @return string
*/
public static function encodeString($string)
{
return sha1($string.SALT);
}
public static function generateToken()
{
if (ini_get('open_basedir') === '') {
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// alternative to /dev/urandom for Windows
$token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
} else {
$token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
}
} else {
$token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
}
return str_replace('+', '', $token);
}
/**
* For a given text, we calculate reading time for an article.
*
* @param $text
*
* @return float
*/
public static function getReadingTime($text)
{
return floor(str_word_count(strip_tags($text)) / 200);
}
}

View file

@ -8,6 +8,22 @@ use Pagerfanta\Pagerfanta;
class EntryRepository extends EntityRepository class EntryRepository extends EntityRepository
{ {
/**
* Return a query builder to used by other getBuilderFor* method.
*
* @param int $userId
*
* @return QueryBuilder
*/
private function getBuilderByUser($userId)
{
return $this->createQueryBuilder('e')
->leftJoin('e.user', 'u')
->andWhere('u.id = :userId')->setParameter('userId', $userId)
->orderBy('e.id', 'desc')
;
}
/** /**
* Retrieves unread entries for a user. * Retrieves unread entries for a user.
* *
@ -15,13 +31,12 @@ class EntryRepository extends EntityRepository
* *
* @return QueryBuilder * @return QueryBuilder
*/ */
public function findUnreadByUser($userId) public function getBuilderForUnreadByUser($userId)
{ {
return $this->createQueryBuilder('e') return $this
->leftJoin('e.user', 'u') ->getBuilderByUser($userId)
->where('e.isArchived = false') ->andWhere('e.isArchived = false')
->andWhere('u.id =:userId')->setParameter('userId', $userId) ;
->orderBy('e.id', 'desc');
} }
/** /**
@ -31,13 +46,12 @@ class EntryRepository extends EntityRepository
* *
* @return QueryBuilder * @return QueryBuilder
*/ */
public function findArchiveByUser($userId) public function getBuilderForArchiveByUser($userId)
{ {
return $this->createQueryBuilder('e') return $this
->leftJoin('e.user', 'u') ->getBuilderByUser($userId)
->where('e.isArchived = true') ->andWhere('e.isArchived = true')
->andWhere('u.id =:userId')->setParameter('userId', $userId) ;
->orderBy('e.id', 'desc');
} }
/** /**
@ -47,13 +61,12 @@ class EntryRepository extends EntityRepository
* *
* @return QueryBuilder * @return QueryBuilder
*/ */
public function findStarredByUser($userId) public function getBuilderForStarredByUser($userId)
{ {
return $this->createQueryBuilder('e') return $this
->leftJoin('e.user', 'u') ->getBuilderByUser($userId)
->where('e.isStarred = true') ->andWhere('e.isStarred = true')
->andWhere('u.id =:userId')->setParameter('userId', $userId) ;
->orderBy('e.id', 'desc');
} }
/** /**

View file

@ -1,7 +0,0 @@
entry:
resource: "@WallabagCoreBundle/Controller/EntryController.php"
type: annotation
config:
resource: "@WallabagCoreBundle/Controller/ConfigController.php"
type: annotation

View file

@ -3,38 +3,34 @@
{% block title %}{% trans %}About{% endtrans %}{% endblock %} {% block title %}{% trans %}About{% endtrans %}{% endblock %}
{% block content %} {% block content %}
<h2>{% trans %}About wallabag{% endtrans %}</h2> <h2>{% trans %}Who is behind wallabag{% endtrans %}</h2>
<dl> <dl>
<dt>{% trans %}Developed by{% endtrans %}</dt>
<dd><a href="mailto:nicolas@loeuillet.org">Nicolas Lœuillet</a> — <a href="http://cdetc.fr">{% trans %}website{% endtrans %}</a></dd>
<dd>Thomas Citharel — <a href="https://tcit.fr">{% trans %}website{% endtrans %}</a></dd>
<dd>Jérémy Benoist — <a href="http://www.j0k3r.net">{% trans %}website{% endtrans %}</a></dd>
<dt>{% trans %}And many others contributors ♥{% endtrans %} <a href="https://github.com/wallabag/wallabag/graphs/contributors">{% trans %}on Github{% endtrans %}</a></dt>
<dt>{% trans %}Project website{% endtrans %}</dt> <dt>{% trans %}Project website{% endtrans %}</dt>
<dd><a href="https://www.wallabag.org">https://www.wallabag.org</a></dd> <dd><a href="https://www.wallabag.org">https://www.wallabag.org</a></dd>
<dt>{% trans %}Main developer{% endtrans %}</dt> <dt>{% trans %}License{% endtrans %}: <a href="http://en.wikipedia.org/wiki/MIT_License">MIT</a></dt>
<dd><a href="mailto:nicolas@loeuillet.org">Nicolas Lœuillet</a> — <a href="http://cdetc.fr">{% trans %}website{% endtrans %}</a></dd>
<dt>{% trans %}Contributors ♥:{% endtrans %}</dt> <dt>{% trans %}Version{% endtrans %}: {{ version }}</dt>
<dd><a href="https://github.com/wallabag/wallabag/graphs/contributors">{% trans %}on Github{% endtrans %}</a></dd>
<dt>{% trans %}Bug reports{% endtrans %}</dt>
<dd><a href="https://support.wallabag.org">{% trans %}On our support website{% endtrans %}</a> {% trans %}or{% endtrans %} <a href="https://github.com/wallabag/wallabag/issues">{% trans %}on Github{% endtrans %}</a></dd>
<dt>{% trans %}License{% endtrans %}</dt>
<dd><a href="http://en.wikipedia.org/wiki/MIT_License">MIT</a></dd>
<dt>{% trans %}Version{% endtrans %}</dt>
<dd>{{ version }}</dd>
</dl> </dl>
<p>{% trans %}wallabag is a read-it-later application: you can save a web page by keeping only content. Elements like ads or menus are deleted.{% endtrans %}</p>
<h2>{% trans %}Getting help{% endtrans %}</h2> <h2>{% trans %}Getting help{% endtrans %}</h2>
<dl> <dl>
<dt>{% trans %}Documentation{% endtrans %}</dt> <dt>{% trans %}Documentation{% endtrans %}</dt>
<dd><a href="https://doc.wallabag.org/">Online documentation</a></dd> <dd><a href="https://doc.wallabag.org/en">english</a></dd>
<dd><a href="https://doc.wallabag.org/fr">français</a></dd>
<dd><a href="https://doc.wallabag.org/de">deutsch</a></dd>
<dt>{% trans %}Support{% endtrans %}</dt> <dt>{% trans %}Bug reports{% endtrans %}</dt>
<dd><a href="http://support.wallabag.org/">http://support.wallabag.org/</a></dd> <dd><a href="https://support.wallabag.org">{% trans %}On our support website{% endtrans %}</a> {% trans %}or{% endtrans %} <a href="https://github.com/wallabag/wallabag/issues">{% trans %}on Github{% endtrans %}</a></dd>
</dl> </dl>
<h2>{% trans %}Helping wallabag{% endtrans %}</h2> <h2>{% trans %}Helping wallabag{% endtrans %}</h2>
@ -42,8 +38,10 @@
<p>{% trans %}wallabag is free and opensource. You can help us:{% endtrans %}</p> <p>{% trans %}wallabag is free and opensource. You can help us:{% endtrans %}</p>
<dl> <dl>
<dt><a href="{{ paypal_url }}">{% trans %}via Paypal{% endtrans %}</a></dt> <dt>{% trans %}wallabag is free and opensource. You can help us:{% endtrans %}</dt>
<dd>by contributing to the project: <a href="https://github.com/wallabag/wallabag/issues/1254">an issue lists all our needs</a></dd>
<dd><a href="{{ paypal_url }}">{% trans %}via Paypal{% endtrans %}</a></dd>
<dt><a href="{{ flattr_url }}">{% trans %}via Flattr{% endtrans %}</a></dt> <dd><a href="{{ flattr_url }}">{% trans %}via Flattr{% endtrans %}</a></dd>
</dl> </dl>
{% endblock %} {% endblock %}

View file

@ -21,7 +21,7 @@
<dt>{% trans %}Developed by{% endtrans %}</dt> <dt>{% trans %}Developed by{% endtrans %}</dt>
<dd><a href="mailto:nicolas@loeuillet.org">Nicolas Lœuillet</a> — <a href="http://cdetc.fr">{% trans %}website{% endtrans %}</a></dd> <dd><a href="mailto:nicolas@loeuillet.org">Nicolas Lœuillet</a> — <a href="http://cdetc.fr">{% trans %}website{% endtrans %}</a></dd>
<dd>Thomas Citharel — <a href="https://tcit.fr">{% trans %}website{% endtrans %}</a></dd> <dd>Thomas Citharel — <a href="https://tcit.fr">{% trans %}website{% endtrans %}</a></dd>
<dd>Jérémy Besnoit — <a href="http://wildtrip.net">{% trans %}website{% endtrans %}</a></dd> <dd>Jérémy Benoist — <a href="http://www.j0k3r.net">{% trans %}website{% endtrans %}</a></dd>
<dt>{% trans %}And many others contributors ♥{% endtrans %} <a href="https://github.com/wallabag/wallabag/graphs/contributors">{% trans %}on Github{% endtrans %}</a></dt> <dt>{% trans %}And many others contributors ♥{% endtrans %} <a href="https://github.com/wallabag/wallabag/graphs/contributors">{% trans %}on Github{% endtrans %}</a></dt>
<dt>{% trans %}Project website{% endtrans %}</dt> <dt>{% trans %}Project website{% endtrans %}</dt>
<dd><a href="https://www.wallabag.org">https://www.wallabag.org</a></dd> <dd><a href="https://www.wallabag.org">https://www.wallabag.org</a></dd>

View file

@ -55,9 +55,9 @@
</div> </div>
<div class="input-field nav-panel-buttom"> <div class="input-field nav-panel-buttom">
<ul> <ul>
<li class="bold"><a class="waves-effect" href="{{ path('new') }}" id="nav-btn-add"><i class="mdi-content-add"></i></a></li> <li class="bold"><a title="{% trans %}Add a new entry{% endtrans %}" class="waves-effect" href="{{ path('new') }}" id="nav-btn-add"><i class="mdi-content-add"></i></a></li>
<li><a class="waves-effect" href="javascript: void(null);" id="nav-btn-search"><i class="mdi-action-search"></i></a> <li><a title="{% trans %}Search{% endtrans %}" class="waves-effect" href="javascript: void(null);" id="nav-btn-search"><i class="mdi-action-search"></i></a>
<li id="button_filters"><a href="#" data-activates="filters" class="nav-panel-menu button-collapse-right"><i class="mdi-content-filter-list"></i></a></li> <li id="button_filters"><a title="{% trans %}Filter entries{% endtrans %}" href="#" data-activates="filters" class="nav-panel-menu button-collapse-right"><i class="mdi-content-filter-list"></i></a></li>
</ul> </ul>
</div> </div>
<form method="get" action="index.php"> <form method="get" action="index.php">

View file

@ -25,4 +25,17 @@ class Utils
// remove character which can broken the url // remove character which can broken the url
return str_replace(array('+', '/'), '', $token); return str_replace(array('+', '/'), '', $token);
} }
/**
* For a given text, we calculate reading time for an article
* based on 200 words per minute.
*
* @param $text
*
* @return float
*/
public static function getReadingTime($text)
{
return floor(str_word_count(strip_tags($text)) / 200);
}
} }