How to add a trailing slash on URL - php

I would like all URLs on my site to have a trailing slash on the URL. I created a simple extension for the URLHelper in Zend. Right now it changes all the word separators to hyphens (-). Adding the functionality to append a trailing slash would be great. The commented out line (hack) didn't work. The slash ended up being url-encoded.
I know this has to be an easy fix and right in front of my face, but it's escaping me. :\
class Ace_Helpers_Url extends Zend_View_Helper_Url
{
/**
* Generates an url given the name of a route.
*
* #access public
*
* #param array $urlOptions Options passed to the assemble method of the Route object.
* #param mixed $name The name of a Route to use. If null it will use the current Route
* #param bool $reset Whether or not to reset the route defaults with those provided
* #return string Url for the link href attribute.
*/
public function url(array $urlOptions = array(), $name = null, $reset = false, $encode = false)
{
if (is_array($urlOptions)) {
foreach ($urlOptions as $index => $option) {
$urlOptions[$index] = trim(strtolower(str_replace(' ', '-', $option)));
#$urlOptions[$index] .= '/'; #Add trailing slash for continuity
}
}
$router = Zend_Controller_Front::getInstance()->getRouter();
return $router->assemble($urlOptions, $name, $reset, $encode);
}
}

Appending it manually isn't really that hard:
<?php echo $this->url(array(
'controller' => 'index'
)).'/'; ?>
If you want to avoid url encoding, check out the fourth parametre to the url helper: encode. Set it to false and it will not url-encode your input, so you can do something like this:
<?php echo $this->url(array(
'alias' => 'blog/2011/09/example-blog-entry'
),'alias',true,false); ?>

My solution is based on this discussion.
application/view/helpers/Url2.php
class Zend_View_Helper_Url2 extends Zend_View_Helper_Url
{
/**
* Keeps Url()'s original params and their default values, so we don't have to
* learn yet another method.
*/
public function url2(array $urlOptions = array(), $name = null, $reset = false, $encode = true)
{
return parent::url($urlOptions, $name, $reset, $encode) . '/';
}
}
in some controller
echo $this->view->url2(array('controller' => 'index'));
or some view
echo $this->url2(array('controller' => 'index'));

Related

Laravel assert redirect with regular expression

I am newly trying out TDD with laravel, and I want to assert if a redirect took a user to a url that has an integer param.
I wonder if I could use regex to catch all positive integers.
I'm running this app with the laravel 5.8 framework and I know that the url parameter is 1 because I refresh the database each for each test, so setting the redirect url as /projects/1 works but this sort of hardcoding feels weird.
I've attached a block of code I tried using regex for but this doesn't work
/** #test */
public function a_user_can_create_projects()
{
// $this->withoutExceptionHandling();
//If i am logged in
$this->signIn(); // A helper fxn in the model
//If i hit the create url, i get a page there
$this->get('/projects/create')->assertStatus(200);
// Assumming the form is ready, if i get the form data
$attributes = [
'title' => $this->faker->sentence,
'description' => $this->faker->paragraph
];
//If we submit the form data, check that we get redirected to the projects path
//$this->post('/projects', $attributes)->assertRedirect('/projects/1');// Currently working
$this->post('/projects', $attributes)->assertRedirect('/^projects/\d+');
// check that the database has the data we just submitted
$this->assertDatabaseHas('projects', $attributes);
// Check that we the title of the project gets rendered on the projects page
$this->get('/projects')->assertSee($attributes['title']);
}
I expected the test to treat the argument in assertRedirect('/^projects/\d+'); as regex and then pass for any url like /projects/1 so far it ends in a number, but it takes it as a raw string and expects a url of /^projects/\d+
I'd appreciate any help.
After watching a tutorial by Jeffery Way, he talked about handling this issue.
Here's how he solves the situation
//If we submit the form data,
$response = $this->post('/projects', $attributes);
//Get the project we just created
$project = \App\Project::where($attributes)->first();
// Check that we get redirected to the project's path
$response->assertRedirect('/projects/'.$project->id);
This is not possible by now. You need to test the Location header in the response with a regular expression.
This is a problem because you cann't use the current route name. That's why I did two functions that bring a little bit of readability to your test. You will use this function like this:
// This will redirect to some route with an numeric ID in the URL.
$response = $this->post(route('groups.create'), [...]);
$this->assertResponseRedirectTo(
$response,
$this->prepareRoute('group.detail', '[0-9]+'),
);
This is the implementation.
/**
* Assert whether the response is redirecting to a given URI that match the pattern.
*/
public function assertResponseRedirectTo(Illuminate\Testing\TestResponse\TestResponse $response, string $url): void
{
$lastOne = $this->oldURL ?: $url;
$this->oldURL = null;
$newLocation = $response->headers->get('Location');
$this->assertEquals(
1,
preg_match($url, $newLocation),
sprintf('Should redirect to %s, but got: %s', $lastOne, $newLocation),
);
}
/**
* Build the pattern that match the given URL.
*
* #param mixed $params
*/
public function prepareRoute(string $name, $params): string
{
if (! is_array($params)) {
$params = [$params];
}
$prefix = 'lovephp';
$rep = sprintf('%s$&%s', $prefix, $prefix);
$valuesToReplace = [];
foreach ($params as $index => $param) {
$valuesToReplace[$index] = str_replace('$&', $index . '', $rep);
}
$url = preg_quote(route($name, $valuesToReplace), '/');
$this->oldURL = route($name, $params);
foreach ($params as $index => $param) {
$url = str_replace(
sprintf('%s%s%s', $prefix, $index, $prefix),
$param,
$url,
);
}
return sprintf('/%s/', $url);
}

Unit test: using the proper terminology for mocking/stubbing

After fundamental changes on my project system architecture, I find myself in a situation where I would need to create "fake" implementation in order to test some functionality that used to be public like the following:
/**
* Display the template linked to the page.
*
* #param $newSmarty Smarty object to use to display the template.
*
* #param $parameters associative Array containing the values to pass to the template.
* The key is the name of the variable in the template and the value is the value of the variable.
*
* #param $account child class in the AccountManager hierarchy
*
* #param $partialview String name of the partial view we are working on
*/
protected function displayPageTemplateSmarty(Smarty &$newSmarty, array $parameters = array(), AccountManager $account = NULL, string $partialview = "")
{
$this->smarty = $newSmarty;
if (is_file(
realpath(dirname(__FILE__)) . "/../../" .
Session::getInstance()->getCurrentDomain() . "/view/" . (
!empty($partialview) ?
"partial_view/" . $partialview :
str_replace(
array(".html", "/"),
array(".tpl", ""),
Session::getInstance()->getActivePage()
)
)
)) {
$this->smarty->assign(
'activeLanguage',
Session::getInstance()->getActiveLanguage()
);
$this->smarty->assign('domain', Session::getInstance()->getCurrentDomain());
$this->smarty->assign(
'languages',
Languagecontroller::$supportedLanguages
);
$this->smarty->assign(
'title',
Languagecontroller::getFieldTranslation('PAGE_TITLE', '')
);
$this->smarty->assign_by_ref('PageController', $this);
$htmlTagBuilder = HTMLTagBuilder::getInstance();
$languageController = LanguageController::getInstance();
$this->smarty->assign_by_ref('htmlTagBuilder', $htmlTagBuilder);
$this->smarty->assign_by_ref('languageController', $languageController);
if (!is_null($account)) {
$this->smarty->assign_by_ref('userAccount', $account);
}
if (!is_null($this->menuGenerator)) {
$this->smarty->assign_by_ref('menuGenerator', $this->menuGenerator);
}
foreach ($parameters as $key => $value) {
$this->smarty->assign($key, $value);
}
$this->smarty->display((!empty($partialview) ?
"partial_view/" . $partialview :
str_replace(
array(".html", "/"),
array(".tpl", ""),
Session::getInstance()->getActivePage()
)
));
}
}
In this case, the PageController class used to be called directly in controllers, but is now an abstract class extended by the controllers and my unit tests can no longer access the method.
I also have methods like this one in my new session wrapper class that can only be used in very specific context and for which I really need to create fake page implementation to test them.
/**
* Add or update an entry to the page session array.
*
* Note: can only be updated by the PageController.
*
* #param $key String Key in the session array.
* Will not be added if the key is not a string.
*
* #param $value The value to be added to the session array.
*
* #return Boolean
*/
public function updatePageSession(string $key, $value)
{
$trace = debug_backtrace();
$updated = false;
if (isset($trace[1]) and
isset($trace[1]['class']) and
$trace[1]['class'] === 'PageController'
) {
$this->pageSession[$key] = $value;
$updated = true;
}
return $updated;
}
Even though I read a few article, it is still quite unclear in my mind if those fake classes should be considered as "stub" or a "mock" (or even "fake", "dummy" and so on).
I really need to use the proper terminology since my boss is expecting me (in a close future) to delegate most of my workload with oversea developers.
How would you call those fake class implementation created solely for testing purpose in order to be self-explanatory?
Gerard Meszaros explains the terminology of dummies, stubs, spies, mocks, and fakes here.
You can find examples from the PHP world here.

preg_replace_callback not returning the correct data

This is in relation to a previous question I asked before: Replacing named 'parameters' within a string in PHP
That little class used to work, but it seems to be misbehaving, now that I'm attempting to move it over to the new Bolt CMS as an extension.
The intention is to grab data from a YML file. The data looks like so:
redirects:
contentToPage:
from: "content/(slug:any)"
to: "page/{slug}"
The extension loops through this data and compares it to the current Request URI, obtained from the applicable Symfony component. If there is a match, the user will be redirected accordingly. So, in this case, if a user tried to visit content/test, they would be redirected to page/test.
Something seems to be going wrong though, where the converted replacement isn't correct, or I get thrown an error. First, here's the block in question:
$convertedReplacements = preg_replace_callback("/^{$convertedPlaceholders}$/", function ($captures) {
$result = $this->destination;
for ($c = 1, $n = count($captures); $c < $n; ++$c) {
$value = array_shift($this->computedReplacements);
$result = str_replace("\{$value\}", $captures[$c], $result);
}
return $result;
}, $requestUri);
$convertedPlaceholders contains the replaced parameters in the from value. So, (slug:any) would be replaced with ([a-z0-9\.\-\_\%\=]+). Now, that works, but the function throws this exception: preg_replace_callback(): Unknown modifier '('.
However, if I change the regex delimiters from / to ~ or #, I don't get the error. Instead, I get the value of the to property in the YML file. In this case, I get page/{slug} and not page/test.
I must be doing something stupid, and I have no idea what it is. For all I know, there's just something that I left out that I can't see.
Here's the entire extension:
<?php
// Redirector Extension for Bolt
// Minimum version: 1.2
namespace Redirector;
use Silex\Application as Application;
use Symfony\Component\HttpFoundation\Request as Request;
use Symfony\Component\Yaml\Parser as Parser;
use Bolt\BaseExtension as BoltExtension;
class Extension extends BoltExtension
{
protected $placeholders = array(
':all' => '.*',
':alpha' => '[a-z]+',
':alphanum' => '[a-z0-9]+',
':any' => '[a-z0-9\.\-\_\%\=]+',
':num' => '[0-9]+',
':segment' => '[a-z0-9\-\_]+',
':segments' => '[a-z0-9\-\_\/]+'
);
protected $computedReplacements;
protected $destination;
/**
* Basic information about the extension. Shown in the Bolt Admin Environment.
*
* #return Array
*/
function info() {
$data = array(
'name' => 'Redirector',
'version' => '0.1',
'author' => 'Foundry Code - Mike Anthony',
'description' => 'An extension that allows you to perform any pre-app <code>301 Moved Permanently</code> redirects.',
'type' => 'Pre-app Hook',
'link' => 'http://code.foundrybusiness.co.za/extensions/bolt-redirector',
'first_releasedate' => '2013-08-28',
'latest_releasedate' => '2013-08-28',
'required_bolt_version' => '1.2',
'highest_bolt_version' => '1.2'
);
return $data;
}
/**
* Initialise the extension's functions
*
* #return void
*/
function initialize() {
$this->options = $this->config['options'];
$this->redirects = $this->config['redirects'];
$this->handleRedirects();
}
/**
* Check for a redirect. If it exists, then redirect to it's computed replacement.
*
* #return ? Response
*/
function handleRedirects()
{
$redirector = $this;
$this->app->before(function (Request $request) use ($redirector) {
if (empty($redirector->redirects)) {
return;
}
$requestUri = trim($request->getRequestUri(), '/');
$availablePlaceholders = '';
foreach ($this->placeholders as $placeholder => $expression) {
$availablePlaceholders .= ltrim("$placeholder|", ':');
}
$availablePlaceholders = rtrim($availablePlaceholders, '|');
//die($availablePlaceholders);
$pattern = '/\{(\w+):('.$availablePlaceholders.')\}/';
//die($pattern);
foreach ($this->redirects as $redirectName => $redirectData) {
$this->computedReplacements = array();
$this->destination = $redirectData['to'];
$from = rtrim($redirectData['from'], '/');
$to = rtrim($redirectData['to'], '/');
$convertedPlaceholders = preg_replace_callback($pattern, function ($captures) {
$this->computedReplacements[] = $captures[1];
return '(' . $this->placeholders[":{$captures[2]}"] . ')';
}, $from);
//die($convertedPlaceholders);
$convertedReplacements = preg_replace_callback("/^{$convertedPlaceholders}$/", function ($captures) {
$result = $this->destination;
for ($c = 1, $n = count($captures); $c < $n; ++$c) {
$value = array_shift($this->computedReplacements);
$result = str_replace("\{$value\}", $captures[$c], $result);
}
return $result;
}, $requestUri);
die($convertedReplacements);
if (preg_match("~^{$convertedPlaceholders}$~i", $requestUri)) {
return $this->app->redirect("/$convertedReplacements", 301);
}
}
}, Application::EARLY_EVENT);
}
}
Any ideas as to what I can do here?
If you want to use / as regex delimiter, you should escape all the / characters in the regex, or, better, use another delimiter as you did.
Right, it seems to be working now. For some reason, everything hopped into place when I changed:
str_replace("\{$value\}", $captures[$c], $result);
to
str_replace('{' . $value . '}', $captures[$c], $result);
I see it has something to do with escaping, which is weird. In the first one, I used double quotes, and escaped the curly brackets so that it would render as {slug}. But, for some reason, that did not happen. Any ideas as to why?

How to pass a URL as a parameter of a controller's method in codeigniter

I have a Codeigniter controller which takes a full URL as the first argument, but the passed URL inside my controller only is only showing http:
public function mydata($link)
{
echo $link; //then it show only http: rather than the full url http://abc.com
}
How can i solve this issue?
if you want to pass url as parameters then use
urlencode(base64_encode($str))
ie:
$url=urlencode(base64_encode('http://stackoverflow.com/questions/9585034'));
echo $url
result:
aHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy85NTg1MDM0
then you call:
http://example.com/mydata/aHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy85NTg1MDM0
and in your controller
public function mydata($link)
{
$link=base64_decode(urldecode($link));
...
...
...
you have an encoder/decoder here:
http://www.base64decode.org/
In Codeigniter controllers, each method argument comes from the URL separated by a / slash. http://example.com
There are a few different ways to piece together the the arguments into one string:
public function mydata($link)
{
// URL: http://example.com/mysite/mydata/many/unknown/arguments
// Ways to get the string "many/unknown/arguments"
echo implode('/', func_get_args());
echo ltrim($this->uri->uri_string(), '/');
}
However:
In your case, the double slash // may be lost using either of those methods because it will be condensed to one in the URL. In fact, I'm surprised that a URL like:
http://example.com/mydata/http://abc.com
...didn't trigger Codeigniter's "The URI contains disallowed chatacters" error. I'd suggest you use query strings for this task to avoid all these problems:
http://example.com/mydata/?url=http://abc.com
public function mydata()
{
$link = $this->input->get('url');
echo $link;
}
Aside from the issue of whether you should be passing a URL in a URL think about how you are passing it:
example.com/theparameter/
but your URL will actually look like
example.com/http://..../
See where you're going wrong yet? The CodeIgniter framework takes the parameter out of the URL, delimited by slashes. So your function is working exactly as it should.
If this is how you must do it then URL encode your parameter before passing it.
You can try this. It worked fr me.
"encode" the value before passing
$value = str_replace('=', '-', str_replace('/', '_', base64_encode($album)));
"decode" the value after receiving
$value = base64_decode(str_replace('-', '=', str_replace('_', '/', $value)));
reference: https://forum.codeigniter.com/printthread.php?tid=40607
I did like #user72740's until I discovered that it can still produce characters not permitted by CI like %.
What I ended up doing is converting the segment string into a hex, them back.
So I created a MY_URI that extended CI_URI and added these methods:
/**
* Segmentize
*
* Makes URI segments, CI "segment proof"
* Removes dots and forwardslash leaving ONLY hex characters
* Allows to pass "anything" as a CI URI segment and coresponding function param
*
* #access public
* #return string
*/
public function segmentize($segment){
if(empty($segment)){
return '';
}
return bin2hex($segment);
}
/**
* Desegmentize
*
* #access public
* #return string
*/
public function desegmentize($segment){
if(empty($segment)){
return '';
}
return $this->hex2bin($segment);
}
/**
* hex2bin
*
* PHP 5.3 version of 5.4 native hex2bin
*
* #access public
* #return string
*/
public function hex2bin($hex) {
$n = strlen($hex);
$bin = '';
$i = 0;
while($i < $n){
$a = substr($hex, $i, 2);
$c = pack('H*', $a);
if ($i == 0){
$bin = $c;
}
else {
$bin .= $c;
}
$i += 2;
}
return $bin;
}
Then used $this->uri->segmentize($url) to create the segment string and
$this->uri->desegmentize($this->input->post('url', true)) to get it back into readable format.
Thus
https://www.example.com/somewhere/over/the/rainbow
becomes
68747470733a2f2f7777772e6d79736974652e636f6d2f736f6d6577686572652f6f7665722f7468652f7261696e626f77
and back.
I am sure there is a better way, like a base_convert() implementation, because this way the string can get arbitrarily long. But now I dont have to worry about = signs and padding, etc.

Send single object from model in method within CakePHP

I'm basically formatting urls before sending my object to the view to loop through (with a foreach() on $submissions. The problem I'm having is that parse_url() takes a single index and not an entire array object.
I've got this method in my SubmissionsController:
public function newest() {
$submissions = $this->Submission->find('all', array(
'conditions' => array('Submission.approved' => '1'),
'order' => 'Submission.created DESC'
));
$this->set('submissions', $submissions);
$this->set('sourceShortUrl', AppController::shortSource($submissions));
}
In my AppController I've got this method which returns the formatted url:
protected function shortSource($source) {
return $sourceShortUrl = str_ireplace('www.', '', parse_url($source, PHP_URL_HOST));
}
This works for single entries, but parse_url can't take arrays, so is there a way in the controller to send the index of the object? E.g. $submissions['Submission']['source'] before I loop through it in the view?
My alternative was to do something like this in my shortSource($source) method:
if (is_array($source)) {
for ($i = 0; $i < count($source); $i++) {
return $sourceShortUrl = str_ireplace('www.', '', parse_url($source[$i]['Submission']['source'], PHP_URL_HOST));
}
}
But that's just returning the first (obviously). What is the best way to do this?
You're on the right track. Check for an array. If it's an array, call it recursively for each item in the array.
/**
* shortSource
*
* Returns an array of URLs with the www. removed from the front of the domain name.
*
* #param mixed $source Either a string or array
* #return mixed $sourceShortUrl An array of URLs or a single string
*/
protected function shortSource($source) {
if (is_array($source)) {
foreach ($source as $url) {
$sourceShortUrl[] = $this->shortSource($url);
}
} else {
$sourceShortUrl = str_ireplace('www.', '', parse_url($source, PHP_URL_HOST));
}
return $sourceShortUrl;
}
In this recursive function, it will parse a single string or an array of strings.
// in the view
if (is_array($sourceShortUrl)) {
foreach ($sourceShortUrl as $url) {
// view specific code for URL here
}
}

Categories