Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
    Tom Butler
    @TRPB

    @conleec A DI container's job is to inject fully constructed objects directly into constructors.

    Being able to inject fully constructed instances into a DI container is arguably a DIC anti-pattern because the DIC is no longer doing it's job: the instance is managed somewhere else, and if that needs its own dependencies they have to come from somewhere. As the application grows this can result in duplicated code.

    You should define the arguments for the object in the container. Either through autowiring:

    class View {
    
        public function __construct(Twig $twig) {
            //...
        }
    }
    
    
    // Let dice read the type hint and automatically create a Twig instance
    $view = $dice->create('View');

    If you need some configuration for Twig you can define the constructor argumnets:

    
    class View {
    
        public function __construct(Twig $twig) {
            //...
        }
    }
    
    
    
    
    $dice->addRules(['Twig' => [
                'constructParams' => ['Foo', 'Bar']
        ]
    ]);
    
    // Let dice read the type hint and create the twig instance. Equvalent of `new Twig('Foo', 'Bar');
    $view = $dice->create('View');
    Chris Conlee
    @conleec
    Tom, thank you SO MUCH for attempting to set me straight on this. I apologize if I'm coming across as a dummy, but something basic is just escaping me here. Since Twig is a package that I didn't write, in what way do I configure Dice to 'autowire' it? Are you suggesting I should create a NEW class that extends Twig? My head is spinning. I'm quite certain it's not as difficult as I'm making it out to be, but dang if I'm confused as all hell anyway.

    If the following is my Twig initialization, how would one set out to 'autowire' this using Dice rules, so I can inject it into my view classes?

        $loader = new FilesystemLoader('views/home');
        $twig = new Environment($loader, [
            'debug' => true,
            'cache' => false
        ]);
        $twig->addGlobal('session', $_SESSION);
        $twig->addGlobal('flash', $_SESSION['flash_messages']);

    Thank you in advance.

    Chris Conlee
    @conleec

    Here's what I've been trying. Am I close at all?

        use Dice\Dice;
        use Twig\Environment;
        use Twig\Loader\FilesystemLoader;
    
        $f3=Base::instance();
        $dice = new Dice();
    
        $dice->addRules([
            '$MyLoader' => [
                'instanceOf' => 'MyLoader',
                'constructParams'  => 'views/home'
            ],
    
            'View' => [
                'constructParams' => [
                    Dice::INSTANCE => '$MyLoader',
                    [
                        'debug' => true,
                        'cache' => false
                    ]
                ]
            ]
        ]);
    
        $view = $dice->create('View');
        dnd(["View of container instance:", $view]);

    And here are my extended classes.

    <?php
    
    
        namespace Controllers;
    
    
        use Twig\Environment;
        use Twig\Loader\FilesystemLoader;
    
        class View extends Environment {
    
        }
    
        class MyLoader extends FilesystemLoader {
    
        }

    Btw, my dnd() function is just a var_dump with some HTML formatting so I can see what the object contains. It's obviously NOT working, as the options are NOT being assigned. The good news is that the view object IS being created. Just not with the necessary params applied.

    bobate
    @bobate

    @TRPB @TRPB
    Tom, thanks for getting back to me. I apologize for the length of this up front. It always takes me about a week to respond because in trying to explain my problem, I often find the solution, such as this time. My Routes were giving 404 errors after implementing url rewriting. I'm not using Homestead/Workbench (already using xampp for a year+) so I had to change DocumentRoot to "C:/xampp/htdocs/Project/public" in order for routes to work. I also added .htaccess (that part was not clear). So my original problem is solved, but I continue to run into different snags as I go along, like now, of course.

    I thank you greatly for this book and the experience it's giving me. I'm a newbie in webdev, just now learning OOP (from you). I am mostly able to follow the reasoning, although I usually don't truly understand until the 2nd/3rd reading (if then). My biggest problem is remembering the logic (which object/array/function/variable came from where and what they do) in the midst of trying to absorb later chapters. If you have any tips to overcome that difficulty I might get to Ninja status. I DO closely compare, or outright copy, your entire code from Github each time you cite a branch. I usually hit some kind of snag at each branch.

    Main problem
    Right now I'm stuck at the branch Relationships-Cached and the joke/list route gives me:
    "Trying to get property 'email' of non-object in C:\xampp\htdocs\Project\templates\jokes.html.php on line 9."
    Same error message for 'name' property.
    The relevant code is:
    (by <a href="mailto:<?=htmlspecialchars($joke->getAuthor()->email, ENT_QUOTES, 'UTF-8'); ?>">
    <?=htmlspecialchars($joke->getAuthor()->name, ENT_QUOTES, 'UTF-8'); ?></a>
    But the joketext, jokeid, jokedate all show up as expected, so joke is a valid object(?). I assume the problem is in the getAuthor method. But I just can't track it down, get lost following the trail. How would I track it down?
    Is there any chance the error is caused by having two Joke.php files (Entity/ and Controllers/)?

    Other problem - Logging out.
    This one seems solved now but with some uncertainty I hope you can remove. The code in the book and Github (Sessions-Logout) only use unset($_SESSION) in the Login.php method logout(). Later branches add session_destroy() so I'm not sure if it was supposed to be there all along?
    Also, what's the difference between using unset(($_SESSION) and $_SESSION = [];?

    Also
    At "Making Use of the Authentication Class," about page 474, the book seems to have redundant code:
    "In Entrypoint.php, add a check that looks for the login key in the route array.
    If it’s set, and it’s set to true" …
    The code seems to be redundant in book and in at least some branches:
    if (
    isset($routes[$this->route]['login']) &&
    isset($routes[$this->route]['login']) &&
    !$authentication->isLoggedIn()
    etc
    So just remove the second isset, or the whole line? It appears the entire 2nd line is removed in the Final-Website branch on Github.

    Thanks much for your help
    Nate

    Tom Butler
    @TRPB
    @conleec can you provide the constructors for both the View and MyLoader classes?

    @bobate one of the chapters (sorry, it was nearly 3 years ago when I wrote that book!) gets you to convert arrays into entity objects. $joke should now be an instance of Joke and $joke->getAuthor() should return an instance of Author

    Other problem - Logging out.

    This was fixed in the sample code and later revisions of the PDF version of the book, it's likely print versions still have the old, incorrect code which was missing session_destroy() as you've pointed out.

    The code seems to be redundant in book and in at least some branche

    Yes, you are correct, this was a mistake in the book, again, fixed in the downloadable PDF on sitepoint.com but not in older versions.

    Sy Moen
    @sy.moen_gitlab
    @TRPB hey, just read half your blog in one go... damn, then I signed up for gitlab just so I could say thanks.
    Really enjoyed the MVC vs MVVM post... it was really fantastic. Thoroughly enjoyed. You into crypto? If so, I'll buy you lunch... send me an ETH address or whaterver.
    Q: do you have a framework skeleton that you commonly work from? Love to see it if you do. Thanks
    Chris Conlee
    @conleec

    @conleec can you provide the constructors for both the View and MyLoader classes?

    I have been able to register Twig on Auryn and successfully make autowiring work. However, I would be very interested in figuring out Dice as well, so I can make an informed decision about which I will use going forward. Here is the constructor for the FileSystemLoader:

    class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
    {
        /** Identifier of the main namespace. */
        const MAIN_NAMESPACE = '__main__';
    
        protected $paths = [];
        protected $cache = [];
        protected $errorCache = [];
    
        private $rootPath;
    
        /**
         * @param string|array $paths    A path or an array of paths where to look for templates
         * @param string|null  $rootPath The root path common to all relative paths (null for getcwd())
         */
        public function __construct($paths = [], string $rootPath = null)
        {
            $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR;
            if (false !== $realPath = realpath($rootPath)) {
                $this->rootPath = $realPath.\DIRECTORY_SEPARATOR;
            }
    
            if ($paths) {
                $this->setPaths($paths);
            }
        }

    And here is the constructor for Twig's Environment class...

    class Environment
    {
        const VERSION = '2.12.1';
        const VERSION_ID = 21201;
        const MAJOR_VERSION = 2;
        const MINOR_VERSION = 12;
        const RELEASE_VERSION = 1;
        const EXTRA_VERSION = '';
    
        private $charset;
        private $loader;
        private $debug;
        private $autoReload;
        private $cache;
        private $lexer;
        private $parser;
        private $compiler;
        private $baseTemplateClass;
        private $globals = [];
        private $resolvedGlobals;
        private $loadedTemplates;
        private $strictVariables;
        private $templateClassPrefix = '__TwigTemplate_';
        private $originalCache;
        private $extensionSet;
        private $runtimeLoaders = [];
        private $runtimes = [];
        private $optionsHash;
    
        public function __construct(LoaderInterface $loader, $options = [])
        {
            $this->setLoader($loader);
    
            $options = array_merge([
                'debug' => false,
                'charset' => 'UTF-8',
                'base_template_class' => Template::class,
                'strict_variables' => false,
                'autoescape' => 'html',
                  'cache' => false,
                'auto_reload' => null,
                'optimizations' => -1,
            ], $options);
    
            $this->debug = (bool) $options['debug'];
            $this->setCharset($options['charset']);
            $this->baseTemplateClass = '\\'.ltrim($options['base_template_class'], '\\');
            if ('\\'.Template::class !== $this->baseTemplateClass && '\Twig_Template' !== $this->baseTemplateClass) {
                @trigger_error('The "base_template_class" option on '.__CLASS__.' is deprecated since Twig 2.7.0.', E_USER_DEPRECATED);
            }
            $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
            $this->strictVariables = (bool) $options['strict_variables'];
            $this->setCache($options['cache']);
            $this->extensionSet = new ExtensionSet();
    
            $this->addExtension(new CoreExtension());
            $this->addExtension(new EscaperExtension($options['autoescape']));
            $this->addExtension(new OptimizerExtension($options['optimizations']));
        }

    Lastly, here is how I registered the Twig instance on Auryn and Fat Free Framework's CONTAINER to make the autowiring work...

        use Twig\Environment;
        use Twig\Loader\FilesystemLoader;
    
        $f3 = Base::instance();
        $injector = new Auryn\Injector();
        $loader = new FilesystemLoader('/');
        $view = new Environment($loader, [
            'cache' => false,
            'debug' => true
        ]);
        $view->addGlobal('session', $_SESSION);
        $view->addGlobal('flash', $_SESSION['flash_messages']);
    
        $injector->share($view);
    
        $f3->set('CONTAINER', function ($class) use ($injector) {
            return $injector->make($class);
        });

    Thanks again for you help.

    Chris Conlee
    @conleec
    @TRPB Actually, HERE is the code for the Auryn configuration, sorry. I still had to make an instance to execute the addGlobal method. If there's a way to do that in Dice without actually instantiating the class, I'd love to know. For that matter, if anybody can tell me how to do it in Auryn that would be great too! I played with the $injector->execute() method but was unsuccessful.
        use Twig\Environment;
        use Twig\Loader\FilesystemLoader;
    
        $f3 = Base::instance();
        $injector = new Auryn\Injector();
    
        $injector->alias('Twig\Loader\LoaderInterface', 'Twig\Loader\FilesystemLoader');
        $injector->define('Twig\Loader\FilesystemLoader', [':paths' => '/']);
        $injector->define('Twig\Environment', [
            ':options' => [
                'cache' => false,
                'debug' => true
            ]
        ]);
    
        $view = $injector->make('Twig\Environment');
        $view->addGlobal('session', $_SESSION);
        $injector->share($view);
    Chris Conlee
    @conleec
    @TRPB Here's my attempt at configuring Dice. I'm getting an error, unfortunately...
        $dice = new Dice();
        $dice->addRules([
            'Twig\Loader\FilesystemLoader' => [
                'substitutions' => [
                    'Twig\Loader\LoaderInterface' => 'Twig\Loader\FilesystemLoader'
                ],
                'constructParams' => '/'
            ],
            'Twig\Environment' => [
                'constructParams' => [
                    'instanceOf' => 'Twig\Loader\LoaderInterface',
                    [
                        'debug' => true,
                        'cache' => false
                    ]
                ],
                'call' => [
                    ['addGlobal', ['session', '$_SESSION']]
                ],
                'shared' => true
            ]
        ]);
        $dice->create('Twig\Environment');
        dnd($dice);
    Chris Conlee
    @conleec
    FYI, here's the error that I'm getting consistently when trying to configure Dice for Twig: Argument 1 passed to Twig\Environment::__construct() must implement interface Twig\Loader\LoaderInterface, array given, called in /Applications/MAMP/htdocs/pt-ff3/vendor/level-2/dice/Dice.php on line 118 [/Applications/MAMP/htdocs/pt-ff3/vendor/twig/twig/src/Environment.php:102]
    bobate
    @bobate
    Tom, I found the problem with my $joke->getAuthor: I had authorId (cap I) instead of authorid sprinkled thru a bunch of different places in my code files. Thanks for narrowing it down for me.
    bobate
    @bobate
    @TRPB
    Tom, about that redundant code I ran into in Entrypoint.php in the run method:
    isset($routes[$this->route]['login']) &&
    isset($routes[$this->route]['login']) &&
    It looks like it was first removed in the Relationships-EditPermissions branch. But I'm confused by this because you introduced it by saying "add a check that looks for the login key in the route array. If it’s set, and it’s set to true" …
    Should I keep the second occurrence and just remove the isset function (to determine if login is true), or will it no longer matter at a later point?
    Thanks
    Nate
    Tom Butler
    @TRPB

    Hi Bobate, yes, unfortunately this was removed from later releases of the book but remains in older versions.

    You can safely remove one of the checks.

    bobate
    @bobate
    Thanks. So it doesn't need to be set to true?
    Tom Butler
    @TRPB
    For each route, you can optionally set the login key to true. If it's set to true, it adds a password check to the specified page. You'll want it set to true on pages you only want to be displayed while logged in. Later on, the code is replaced and rather than having a simple true/false different pages can be given different permissions.
    bobate
    @bobate

    Thanks again.
    As I go thru all the code, I can usually understand it. And I greatly appreciate your emphasis on creating the framework and keeping website-specific code separate. But I have trouble following the path of the logic without forgetting where I was two steps ago. I could never build this on my own and I'm in awe of how you can keep it straight in your head to achieve that. Is there perhaps a flow chart, either of IJDB or a general process it fits into? Is that what MVC is all about? Is there any particular reading you could recommend that would help me get this right in my head?

    Ha. Even in trying to write this, some of the concept seems to be dawning on me. For instance, is it correct that save methods are attached to tables instead of jokes so that it's DRY and not every entity to go in a database has to have its own save method? (I'm proud of that!)

    Some more specific questions:
    I have a var_dump($_SESSION) in my layout so I can monitor variables. When I don't know what something is I make it a session variable. But they mostly don't show up. I think it has to do with session_start()? Is there a way to do that when I want to see what the value/purpose of a variable/array?

    Thanks much.
    Nate

    bobate
    @bobate

    To any and all,
    I finished the IJDB framework from Tom's book phpmysql Novice to Ninja 6th ed. (thanks Tom), and hoping a few people can help me out and try to break it. Please go to www.ltvinfo.com/joke/list and see if you can mess it up.

    I'm already aware of at least one bug; If you put a blank route (no route) after the webhost name, my underlying webhost is displayed instead of the ijdb site/app. You have to go back to 'joke/list' or another route to get back to the app. And I had to move jokes.css up to my top directory because layout.html.php wasn’t showing any styles, just straight html. I couldn't figure out where to put .htaccess, or its exact content. After installing all files, it wouldn’t access my ijdb directories, although it runs fine on my localhost. (It also may have something to do with having WordPress installed, which has its own .htaccess.)

    If anyone can speculate what is happening there I'd appreciate. I would appreciate any feedback at all on anything, and any screenpics that might help me improve. I will leave it alone for a few days hoping that someone will be experimenting.
    Thanks
    Nate

    kvnb
    @kvnb
    Hi Nate,
    kvnb
    @kvnb
    There are a few issues that I've found aside from the easy-to-guess admin passwords.
    The most serious is that while you sanitise HTML output on your joke list page you do not seem to in your user list page. It was easy to inject javascript; a security vulnerability.
    You accept formatting marks as username. You can see examples of this on IDs -2 and 120. You allow any numerical ID when a joke is submitted allowing ID reuse (see 3) and negative IDs (-1 and -2). Is that intended?
    bobate
    @bobate
    Wow, thanks for checking K. None of that was intended, and in fact since this is really my first project, I don't know what most of it means. Read about them, but never experienced, so new to me. The site is almost straight out of Tom's book, with a few minor tweaks, so if you could expound and advise, I would REALLY appreciate it.
    Are all your points about sanitizing? And is that just htmlentities or other processes? Thanks
    kvnb
    @kvnb
    Cool. Then this may be more detail than you need. It's not badly put together for a first project and it's more important to understand the MVC implementation at this point.
    The most important point is the first as it allows me to inject javascript onto your page. That is a sanitisation issue. htmlentities is sensible but it depends on how user input gets displayed as to whether it is effective.
    The formatting mark issue is less about sanitising and more about data validation. It doesn't make sense to have a left-to-right mark (markup punctuation) as a username so it shouldn't be allowed in the first place. Consider limiting what is accepted as input.
    Roughly the same issue stands with allowing me to specify an ID when creating a joke. An ID should be assigned when a joke is created and any ID I submit should match with a joke that exists and that I can edit or delete.
    treggi12
    @treggi12
    hi, a minor issue (not security) is that "the number of total jokes" not change when you select a category
    bobate
    @bobate

    tregg
    thanks for looking. Yes, I saw that too. I've copied all this code from the book, and just copying was extremely complex, but that's the way the code is. I could probably fix it, but since this is just a practice lesson, I didn't bother.

    kvnb
    Actually, I need as much detail as you can supply. I'm really a complete noobie, learning from books and online, finding it very scattershot. If you can explain how you did all you did that would be great.
    For instance, can you explain where/how you were able to assign negative joke id's, (or any at all since id is auto-increment)? Is that sql injection?
    And how did you access the joke date? Also sql injection?
    Were you only able to do it because you gained admin access?
    I'm reading up and figuring where I need htmlspecchar. Meanwhile any explanations you can provide I would greatly appreciate.

    I think this project does comply with MVC, but I'm not sure I could pinpoint the delineations. The Views are obviously the templates, the Controllers are in the controllers directory, and I guess the rest is Model?, altho I don't actually understand the fine line between Controllers and the Model. Hopefully as I work with it, it will become clear.
    Thanks
    Nate

    treggi12
    @treggi12
    hello Nate, I am in your same condition ... and with the same doubts; I also followed the book by making some small changes (the joke counter by category for example) ... then I was trying to apply the notions to my site but I had to stop for the momentary unavailability of the PC.
    I follow with great interest
    kvnb
    @kvnb

    For instance, can you explain where/how you were able to assign negative joke id's, (or any at all since id is auto-increment)? Is that sql injection?

    Not really. SQL injection is when you include SQL statements in the request content and hope that it will be run. I just changed parameter values. On your joke edit page, if I change the joke id to a negative ID it gets inserted into the database with that value.

    And how did you access the joke date?

    I didn't. The username for that account is a right-to-left character, which flips the direction of the text in the dateline.

    Were you only able to do it because you gained admin access?

    No. I could do this with newly created accounts provided they could edit jokes.

    I'm reading up and figuring where I need htmlspecchar.

    htmlentities should be used whenever user-submitted data is displayed. There are some exceptions to this and it isn't always enough, but that is a good starting point.

    The Views are obviously the templates, the Controllers are in the controllers directory, and I guess the rest is Model?

    Sort of. Tom Butler would be better at explaining this than I am (I read his blog to be better at the meta-design stuff). The model is a big concept - it should include the data for your application but also all of the ways that data can interact or change. Controllers make changes to the model using the interactions and methods for change that the models offer. The views can just be templates but they are in charge of anything related to how the model is returned to the user. That means they can be quite complex (pagination, filtering, sorting, etc all belong in the view).

    bobate
    @bobate

    @kvnb
    "I just changed parameter values. On your joke edit page, if I change the joke id to a negative ID it gets inserted into the database with that value."
    So how do you change parameters? As far as I can see, the joke edit page only allows you to select categories and enter/edit joke text. What code did you enter to do that, and where? I tried changing the id in the url, but it didn’t do anything to the joke's id.

    "The username for that account is a right-to-left character, which flips the direction of the text in the dateline."
    I don't understand this. I'm looking at the author record in phpmyadmin. There is no username for that joke, and email ends in .com. What is the right-to-left char? And where is it, and how is it able to affect the next field?

    kvnb
    @kvnb

    So how do you change parameters? As far as I can see, the joke edit page only allows you to select categories and enter/edit joke text.

    There are two ways. The ID is hidden in an input element that the browser won't display. In your code you'll see:

    <input type="hidden" name="joke[id]" value="">

    I can use my browser's developer tools to change the value:

    <input type="hidden" name="joke[id]" value="-2">

    Any parameter that you receive in the web request can be edited. It's important to be strict about what acceptable values are and only make changes when you are sure the value is within the ranges or limits you expect.

    I'm looking at the author record in phpmyadmin. There is no username for that joke, and email ends in .com. What is the right-to-left char? And where is it, and how is it able to affect the next field?

    There is a username. You just can't see it. I've used a special character, [U+202E], the right-to-left override (http://www.unicode-symbol.com/u/202E.html). Instead of appearing as a letter or symbol this character tells the browser to display text from right to left instead of the usual left to right. I picked a character that changes how text is displayed to make it obvious to you something is wrong.

    There are other special characters that are equally invisible (non-printing) that don't do anything obvious to the surrounding text. These can be used to mimic other users. For example, if you have a username "nate" and I want to pretend to be you, I could sign up with a username of "[U+202D]nate". The left-to-right mark (http://www.unicode-symbol.com/u/202D.html) is also invisible so my username would appear as "nate" while the database would see the names as having different lengths and values.

    There are a lot of these sorts of characters. At this stage it's easiest to only accept certain characters in your code as being valid for usernames. Again, this goes back to only accepting values that make sense. No-one wants a right-to-left override as their username for a good reason.

    bobate
    @bobate

    @kvnb
    I'm grateful for your showing me weaknesses in my code. I'd like to understand better how you did it so I can defend against it, and hoping you could be more explicit re what/where and how you coded. I'm holding off sanitizing/validating entries until I have enough knowledge and won’t need them to analyze. BTW, I have only a little javascript experience.
    http://www.ltvinfo.com/author/accessbroke
    I tried creating a new user with name [U+202E], then just as last char, and without brackets, but that text actually displayed for me, with no affect like yours had. My research didn’t find any explanation how to do that. How exactly did you enter it? And will htmlentities defend that?

    I just found the way to edit html (right-click) in dev tools, didn't know that was there. But how do you send the changes to server? I tried submit button, but changes disappeared, even when I was logged in as authorized on the full access page (not the display model).

    In your initial comment, you said my admin passwords were easy to guess. (True, but it's all practice stuff of course.) But how would you know? Were you able to sign on as one of them? Did you see/find their (fake) emails somehow? In my display-only user access table, you can only see name, so how did you get any other author data?

    Your js code (alert…) in each user category next to check boxes on the one row seems to be part of my foreach loop. How did you do that?
    Thanks much for your time
    Nate

    kvnb
    @kvnb

    I'd like to understand better how you did it so I can defend against it, and hoping you could be more explicit re what/where and how you coded.

    Of course I can. Learning this is the best way to learn how to test your own code!

    I just found the way to edit html (right-click) in dev tools, didn't know that was there. But how do you send the changes to server?

    Depends on the browser you are using but if you use the "Inspect Element" function in your developer tools then you should be able to make changes in place that will work. Alternatively, you can look at your network requests (the "Network" part of your developer tools) and you can usually edit the request and resend it. If you still can't make it work let me know your browser and I can try and link you to a tutorial.

    There are also tools to test websites that make this easier as you go forward (but are not worth learning at this point).

    I tried creating a new user with name [U+202E] ... . How exactly did you enter it? And will htmlentities defend that?

    You need to copy the character from somewhere and paste it into the form. You can do it in most word processing applications (insert symbol -> general punctuation). Alternatively, the site I linked to has a text box that includes the character (remember it won't be visible). Click in the box, right-click to "select all" and pasting will copy it across.

    htmlentities does not encode that character, but even if it did there would still be the problem that users could create names that appear identical. The solution here is to not allow the character.

    Your js code (alert…) in each user category next to check boxes on the one row seems to be part of my foreach loop. How did you do that?

    I didn't. Your foreach loop plonks the author name into every table cell as a hidden element:

    <input type="hidden" id="author" name="author" value="">

    For that author the name is:

    <script type="text/javascript">alert("Hi")</script>

    Unfortunately, the inclusion of the " mark in the author name closes the value parameter in the hidden element early. The browser doesn't know what to do with the rest so makes a best guess, closes the hidden element and pops the leftover text into the table cell as-is, in this case alert("Hi")">.

    It's just another instance where not validating or sanitising the data has led to an unexpected outcome.

    In your initial comment, you said my admin passwords were easy to guess. But how would you know?

    Your jokes list has a mailto address anchor for each author name. wendy@wendy.wendy caught my eye and seemed worth a try. Your browser can remember passwords for you - best to use strong passwords on any site you build or sign up to.

    bobate
    @bobate
    @kvnb
    Wow, what a lesson. Several mysteries solved. Thanks. I didn’t know dev tools could do all that. If you have a link to a good tutorial for that I'd appreciate it. I should have known about the foreach since I coded that myself. And I knew the emails were available somewhere, couldn't remember.
    So now I can get about validating. I will post again when hopefully I have cleaned it all up. Thanks again.
    kvnb
    @kvnb
    @bobate Pleased to have helped :). I like the mozilla docs and they have some good pages on developer tools. A good start would be https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools. Best of luck!
    bobate
    @bobate
    @kvnb
    "htmlentities does not encode that character, but even if it did there would still be the problem that users could create names that appear identical. The solution here is to not allow the character."
    So how do I prevent that character and any others that allow users to mess up the system? Do I have to address them individually, or is there a function that will do it similar to htmlentities()?
    Thanks
    Nate
    kvnb
    @kvnb

    @bobate

    Do I have to address them individually, or is there a function that will do it similar to htmlentities()?

    This is a whole topic in itself and I'm happy to help, but a few posts ago you wrote the following:

    I'm holding off sanitizing/validating entries until I have enough knowledge and won’t need them to analyze.

    It is validation that we are talking about here, so worth thinking if you want to spend time on it now as opposed to other things you are trying to learn.

    Validation is about making sure that input from a user matches what you expect to get before you use or save it. There are a few ways you can do it, but usually it involves writing a pattern and using dedicated functions to see if the input matches that pattern. In some instances, PHP already has functions in place to check this for you.

    For example:

    filter_var($email, FILTER_VALIDATE_EMAIL)

    Will validate most valid email addresses.

    For integer IDs I would use the following:

    filter_var($id, FILTER_VALIDATE_INT, array("options" => array("min_range" => 0)));

    This will reject negative IDs as well as IDs containing text. I would do something like the following to start:

    $id = filter_var($id, FILTER_VALIDATE_INT, array("options" => array("min_range" => 0))); if($id !== FALSE) { // Code for valid ID }

    Note the importance of === instead of ==. PHP is designed to push on with your code no matter what, so will squint hard to make variables match unless you tell it not to. With ==, PHP will try all sorts of conversions to your variable to see if it can make it match. With === it has to be an exact match. This is important, because 0 might be a valid ID, but can look like a boolean FALSE if == is allowed a say.

    For text input we have to specify our own rules, but filter_var is designed for this. The following will match any string of 1 or more "word" characters - any letter, digit or underscore. Should be good enough for usernames.

    filter_var($username, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => "/^\w+$/i")));

    A REGEXP is a regular expression, or a pattern. The one I have used here follows the following format:

    /.../i uses the / sign to enclose the pattern and finishes with an i to ignore upper vs lower case - any letters in the pattern will match both (not relevant here, but a habit).

    ^...$ says that the pattern has to match the whole string. ^ marks the beginning, $ marks the end.

    \w is the pattern to match any "word" character, and the + sign is an instruction that one or more of that character is needed.

    See how you get along with that and let me know if there's anything you're struggling with.

    bobate
    @bobate

    @kvnb
    I have to say it’s super having a personal tutor. I really appreciate the time you’re giving me.
    Sorry, I wasn’t clear about “holding off.” What I meant was that I didn’t want to delete or fix your hacked entries until I had enough knowledge of the topic to prevent them (so they’re still there now). I intended to use them to experiment with, so, yes, that’s what I’ve been studying all these days. I know I can’t move forward until I have that figured out. I’ve been poring over 1000’s of “helpful” web sites/books, most ambiguous or just poorly explained, often contradicting each other about validating, offering various conflicting “solutions.”

    Regexp:
    So I think you’re suggesting that I validate user’s entry by setting $username = filter_var($username,… then reject the entry if the value is false, or insert it in database if it retains its value? Is that right? I’m having some trouble making that work with a practice form on my localhost. I’ve been playing with regexp on https://regexr.com/, trying to see how it works.

    I’m also contemplating a future project allowing numerous other characters for informal username: letter, digit, underscore, as well as - hyphen, ampersand, space, single quote, period, exclamation, equals, plus, period. It seems period must be escaped, and maybe hyphen also? Getting conflicting results between my practice and regexr.com. I believe my regexp should be
    “/[\w-& '!=+.]+/”
    Does that look right to you? Works on the regexr but not my practice page. Allows other chars, like double quote.?

    Negative id:
    I still don’t know how you entered any joke id at all since it’s auto-increment and there’s no field for it (except hidden). If it’s thru dev tools, I haven’t really figured that out yet, but I suppose I must still have to validate the id before inserting the joke in the database, is that right? How do I do that if the id isn’t set until it gets to sql? How do I stop you from setting an id?
    Thanks
    Nate

    @kvnb
    I just noticed that 2 escape characters in my regexp disappeared when I entered the text above: before the hyphen and before the period. I tried to edit them back in but they disappeared again. Hmm!
    kvnb
    @kvnb

    I have to say it’s super having a personal tutor. I really appreciate the time you’re giving me.

    You're welcome. I'll do my best to help where I can but I'm still learning myself.

    So I think you’re suggesting that I validate user’s entry by setting $username = filter_var($username,… then reject the entry if the value is false, or insert it in database if it retains its value? Is that right?

    Yep. Good summary.

    I’m having some trouble making that work with a practice form on my localhost.

    What's not working, and what's the code? If you're happy to share snippets it'll be easier to figure out what's going on.

    Does that look right to you? Works on the regexr but not my practice page. Allows other chars, like double quote.?

    Regexp testers may not use the same underlying algorithm as PHP. Use your actual environment to test. In this case, [\w] is matching the double quote character, though PHP does not document this.

    I still don’t know how you entered any joke id at all since it’s auto-increment and there’s no field for it (except hidden). If it’s thru dev tools, I haven’t really figured that out yet

    Hidden fields are better thought of as 'not visible in the browser'. Dev tools can see them (use the DOM inspector) and you can still edit them. <input type='hidden' name='id' value='1'> can be changed to whatever you want and then submitted.

    Your auto-increment does not seem to be failsafe. I think you have code that mixes creating new jokes with editing existing jokes. I'd need to see the code to be more specific. Are you in control of each sql statement or are you using something like an ORM?

    Another concern is that you do not seem to be using IDs as they are submitted. If I go to http://www.ltvinfo.com/joke/edit?id=2 I can edit the joke with ID of 2 as expected. If I go to http://www.ltvinfo.com/joke/edit?id=2b I still see an edit page for the joke with ID of 2 as opposed to a 404 or other default view. SQL would not match like this.

    I suppose I must still have to validate the id before inserting the joke in the database, is that right? How do I do that if the id isn’t set until it gets to SQL? How do I stop you from setting an id?

    The simplest method is to only use a user-submitted ID to retrieve a record from the database. Validation can be done by the presence or absence of a record with that ID.

    bobate
    @bobate
    @kvnb
    Sent you a private chat
    Nate
    bobate
    @bobate

    @kvnb

    Sorry for the slow reply

    Forgiven. Lol

    (me) -g- flag. only useful with preg_match?
    That flag exists to allow you to find multiple matches in a string and return each match as a separate array entry. It is useful when you want to extract information from a string that fits a pattern, but is not useful for validation. PHP doesn't use this flag, but other languages do. Use preg_match_all instead of the flag if you need that behaviour. For validation you do not.

    Great lesson. Thanks

    if ($username == filter_var($username …
    You need to use ===. I think a blank username would get through your filters as they stand.

    Thanks, tested this and I see you’re right.

    (me) I got all of that except the submit part. How do you submit it?
    Once I've edited in the DOM inspector I can hit the Submit button on the form same as anyone would.

    Ok!, this finally worked for me. I was certain I tried before and it didn’t post, so I was perplexed. Maybe it was on my dummy form which doesn’t actually submit.
    So now I’m working on how to prevent user submitting altered hidden field. Research (SO) seems to indicate no easy way. I’m testing some ideas, like testing if user’s id matches author of joke with GET id. How do you do it?

    It's risky using code that you don't understand as you can't always tell what it will do.

    I’m kinda happy to say I think I understand each piece of php code pretty well. But I just “learned” OOP from this project, so it’s mostly the strategy of structure and why certain methods go into which classes. Jumping from class to class to method to class, trying to follow trail just to perform a single INSERT is what’s hard for me. So different from procedural (tho I think I see the benefit). And then trying to track down errors in that path. And the OOP: implements, interface, use, autoload, references(&$), dependencies, routes, \stdClass, constructorArgs, \ReflectionClass, and keeping all that in my head. I’m in awe of Tom for having written the book (~700pgs) and built the code in it. That’s genius to me. But his explanations of strategy are often over my head or difficult to keep inside it. It’s kind of like learning a spoken language, waiting for the light bulb to come on. If you can recommend some good, clear books/websites to explain strategy/structure, like where you learned it, that would help tons. I’m always searching.

    It's a little bit of a guess, but from the code you've posted I think the problematic bit is:
    private function insert($fields) {
    How is the $fields variable constructed? If I submit a non-existent ID will there be an ID field in that array?

    The insert($fields) method is part of Tom’s “Ninja framework” in the DatabaseTable class so that it can work in any database application regardless of the number or type of fields in a record. It’s called directly from save($record) method in same class, and the $fields array is POSTed from the forms for jokes, authors and categories, eg $joke = $_POST['joke'];. The forms create arrays, eg name="joke[joketext]". Possible paths are thru addJoke($joke) in Author entity, addCategory($jokeCat) in Joke entity, POSTed from form to saveEdit($category) in Category controller, or POSTed to savePermissions($author) in Register controller.
    Thanks
    Nate

    kvnb
    @kvnb
    So now I’m working on how to prevent user submitting altered hidden field.
    Don't. Users can throw anything at your server they want. Best to accept that and design your application to cope.
    kvnb
    @kvnb

    I’m kinda happy to say I think I understand each piece of php code pretty well.

    Good. I've had a look at the DatabaseTable class and it is the cause of the ID mishap. I'll talk you through it and see if that helps.

        public function save($record) {
            $entity = new $this->className(...$this->constructorArgs);
            try {
                ...
                $insertId = $this->insert($record);
                ...
            }
            catch (\PDOException $e) {
                $this->update($record);
            }
            ...
    }

    This function seems to be trying to INSERT whatever you give it and then UPDATE the record if the query throws an exception (like it would if the record already existed). So when a dud ID is put in it happily INSERTs it. The auto-increment will only work if no ID is specified on the INSERT.

    So, if I edit the ID field, the $_POST['joke[id]'] parameter will have a value, that you pass on to the $fields array, that you put into this function, that tries to INSERT it, which succeeds as INSERT will accept a value for ID, which means you have a record with whatever I put in that fits with SQL's schema for that column (probably an INT).

    kvnb
    @kvnb

    If you can recommend some good, clear books/websites to explain strategy/structure, like where you learned it, that would help tons. I’m always searching.

    To be honest, there's a lot of bad information out there on PHP. I learnt to program in Java which is designed around OOP. That helped. With PHP I mainly learnt by building things. Robert Martin's "Clean Code" book and Martin Fowler's "Patterns of Enterprise Application Architecture" were really helpful and I still refer to those. People like Tom Butler are great to refer to for being very disciplined about how they design applications using OOP, and python helped me to understand the notion of using immutable objects to help with this. I'm still trying to improve my architectural decisions.

    bobate
    @bobate
    @kvnb
    I haven’t been able to get to work on this during holidays. Just getting back to it now, with a quick question.
    I’ve seen a ton of posts about Laravel on places like Reddit, wondering if it’s something I should try. Or should I stick with raw code, thereby learning it better? I don’t need another “language” (learning curve) to try to surmount in the midst of all this. Do you use Laravel or some other framework/library?
    kvnb
    @kvnb

    Do you use Laravel or some other framework/library?

    Sometimes. I've not used Laravel, but I haven't built large projects before from scratch so it would be overkill for me. I'm starting a larger project now in my free time that I want to build using a MVC paradigm broadly in line with Tom Butler's writing (which is why I'm here). Those frameworks would be tricky to adapt to that ideology.

    I must stress that I'm a hobbyist - I program in my free time to make things I find useful to me.

    should I stick with raw code, thereby learning it better?

    I think it depends on what you are trying to do. If you're trying to learn MVC architecture then there's no point using Laravel as they've made all those decisions for you. You'd just be learning how to use Laravel. If you want a framework to help you learn OOP it might be easier to use a smaller framework that you'll find easier to understand. Frameworks are just tools - it's best to know what you want to achieve before picking which tool to use.

    bobate
    @bobate
    Thanks for that guidance. I think I'm going to explore laravel to see what I can achieve. Then hopefully I can learn better by trying to dig into the code.