Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 07/15/2016 in all areas

  1. With support for paginated Fieldtypes, ProcessWire’s scalability has moved to the next level. Not sure what this means? Don’t worry, this post has a screencast that makes it clear. We’ve also got some other nice upgrade for ProFields Table. https://processwire.com/blog/posts/fieldtype-pagination/
    9 points
  2. Also, you can temporary removing SessionHandlerDB and see if that might be the culprit.
    3 points
  3. I didn't have the chance to use it yet, but https://www.import.io/ seems like an excellent tool. Maybe it works!
    2 points
  4. Google changed its use for Google Maps. Since 22 of June 2016 you no longer can use (embed) Google Maps without a registered api key bound to a domain name (called a referrer by Google) Just for the record: in Googles developer page where you have to register a Google Maps api key, they show a misleading dot after the first forward slash in their example: */.domain.com/* Only when I removed that dot my Google Map in a webshop showed up again. Anyway, read it all here: https://developers.google.com/maps/pricing-and-plans/standard-plan-2016-update
    2 points
  5. Using the UTF-8 page-names is exactly for the reason not to replace those characters. There's no option to mix those behaviors by now.
    2 points
  6. Why do you modify core module? You can enter those in the backend on the configuration of this module.
    2 points
  7. Like this? http://modules.processwire.com/modules/inputfield-ace-extended/
    2 points
  8. Awhile back, I made an Ajax API for querying pages in the admin via the ProcessPageSearch module. It is used by [for example] the PageAutocomplete Inputfield. I thought this capability would be useful on the front-end too, so this module brings it to the front-end as a page in your site that you can put wherever you want to. The way you use it is exactly the same as the one in ProcessPageSearch, but this one is a little more strict, given that it's publicly available on the front-end. By "more strict" I mean that you have to define what you want to allow in terms of input and output in the module's configuration. The web service takes it's query from GET variables in the URL and returns results in JSON format. It installs a page called /service-pages/ in your site, and you are welcome to move that page wherever you want. Here is the official page at modules.processwire.com: http://modules.processwire.com/modules/service-pages/ Once installed, you should view the /service-pages/ page that it installs because it outputs detailed instructions and examples on how to use it in your own projects. But here's a few excerpts from what you'll find on that instructions page: Input The /service-pages/ page can be queried with GET variables in the URL to return JSON-format results. The query string should follow a ProcessWire selector format ([field][operator][value]), but modified a bit for use in a URL query string. Here are a few format examples: Specify a single value: ?field=value Specify multiple fields and values to match: ?field1=value1&field2=value2&field3=value3 Specify multiple fields where at least one must match the value. Note use of "," rather than "|", something we had to settle for to make it work as a URL key: ?field1,field2,field3=value Specify one field with multiple possible values (it's fine to use "|" as a separator here): ?field=value1|value2|value3 Note that unlike regular ProcessWire selectors, multiple field=value sets are split with an ampersand "&" rather than a comma ",". Allowed Values The allowed values for field are set with the module configuration. You may also specify the following modifier keyword=value pairs: sort=[field] (Specify field name to sort results by) debug=1 (Enables debug mode producing human readable output) limit=[n] (Specify the max number of pages to return) start=[n] (Specify the result number to start with) include=hidden (Include pages that are 'hidden') Allowed operators The operator demonstrated by the "=" sign in the examples above may be replaced with any of the following operators in the query string: = Equal to != Not equal to < Less than > Greater than <= Less than or equal to >= Greater than or equal to *= Contains the exact word or phrase ~= Contains all the words %= Contains the exact word or phrase (using slower SQL LIKE) ^= Contains the exact word or phrase at the beginning of the field $= Contains the exact word or phrase at the end of the field As an example, this ProcessWire selector: template=property, body*=luxury, bedrooms>5, bathrooms<=3 ...would be specified as a query string to this web service like this: ?template=property&body*=luxury&bedrooms>5&bathrooms<=3 Allowed templates For security, the search will only be performed on pages using templates that are defined in the module's configuration. Output The returned value is a JSON format string in the following format (populated with example values): { selector: "title*=something, template=basic-page, limit=50", total: 2, limit: 50, start: 0, matches: [ { id: 1002, parent_id: 4525, template: "basic-page", path: "/test/hello/", name: "hello" }, { id: 1005, parent_id: 4525, template: "basic-page", path: "/test/contact/", name: "Contact Us" } ] } Each of the 'matches' values will also include all the fields you have specified to appear with the ServicePages module configuration. If an error in the query prevented it from being performed, a JSON string in this format will be returned: { errors: [ "Error message 1", "Error message 2 (if there was one)", "And so on..." ] } The web service honors user view permissions. As a result, if you are accessing this service from a superuser account, you are likely to get pages that others users may not see. Superusers get an "include=all" automatically, unless you override it with an "include=hidden". Returned field values The following field values will be returned for all matched pages: id (integer) parent_id (integer) template (string) path (string) name (string) Any other fields may be included from the module's configuration screen. Pagination To paginate, simplify add a "page[n]" url segment to the request URL, i.e. /service-pages/page2/?template=basic-page&sort=name
    1 point
  9. I'm writing this to give back something to the community that has given so much up front over the past year. I noticed there's hardly any discussion about testing in these forums so I decided to write this quick primer to get some discussion going. I'm by no means an expert on phpunit or selenium but I had to jump through a few hoops to get it working (especially with PHPStorm), so I thought I figured I should share my experiences with the community. Also, I'm hoping non Phpstorm users can still pick something up useful in this post. Prerequisites : It is assumed Phpunit (https://phpunit.de/) is installed via Composer, Selenium (http://www.seleniumhq.org/) and Php-webdriver for Selenium (https://github.com/facebook/php-webdriver) is preinstalled. For Phpstorm users, there's a fairly detailed installation and unit testing instructions here (https://www.jetbrains.com/help/phpstorm/2016.1/testing.html) I found some parts of it leaving me with unanswered questions, so I'm hoping this post will supplement any questions that you might encounter. Rather than writing a single monolithic post, I will write several posts covering different topics.
    1 point
  10. Hey, The Form API has CSRF protection build in, but if you for some reason don't want to use the API you can however use the CSRF protection. Its very simple but it took some time for me to find out, so i figured i share my findings with the rest. What is CSRF? First you need to create a token and a token name you do that as following: $tokenName = $this->session->CSRF->getTokenName(); $tokenValue = $this->session->CSRF->getTokenValue(); Very simple. Now what you want to do is create a hidden input field like this: $html .= '<input type="hidden" id="_post_token" name="' . $tokenName . '" value="' . $tokenValue . '"/>'; Now this will generate something that will look like this: You are done on the form side. You can now go to the part where you are receiving the post. Then use: $session->CSRF->validate(); This will return true (1) on a valid request and an exception on a bad request. You can test this out to open up your Firebug/Chrome debug console and change the value of the textbox to something else. Basicly what this does is set a session variable with a name (getTokenName) and gives it a hashed value. If a request has a token in it it has to have the same value or it is not send from the correct form. Well I hope I helped someone.
    1 point
  11. Should the "New page from inputfield" option work on PW v3? Cause it doesn't seem to work for me. I have this tree for adding taxonomies and terms: Taxonomies > Taxonomy > Terms (these are templates with only the title field). Then i have a field tags of type page with parent Tags (taxonomy template), template set to terms , label is title(default) and input field type asmselect. Taxonomies > Tags > tag1, tag2, tag3 The asmselect field is displayed on the page and populated with the tags but i don't have the option to create a new term. I hope you understand my jumble.
    1 point
  12. Perhaps not particularly helpful (sorry), but to be honest this sounds like something you should always do via an API. If the company hosting those properties intends them to be available to others this way, usually they would provide an API to said content. If they don't, it's possible that they wouldn't like anyone crawling their content either With some quick googling I found some hints that the company behind the actual platform (Immformer) might offer additional features etc. so I wouldn't be too surprised if they offered, either as a part of the platform or as a plugin/addition, some way to export entries. I don't know anything about your client's relation to the site in question and the relation of that site to the platform they're using so it's very difficult to say who you would contact in this case, but anyway: in my opinion the only future-proof, stable solution you're going to find would need co-operation from the management of the NetMakler site and perhaps the provider of the platform they're using. Anything else is, at the very least, very likely to break every time something changes at the site
    1 point
  13. There is SessionHandlerDB and ProcessSessionDB in the modules table. Both belong together, ProcessSessionDB is a submodule of SessionHandlerDB.
    1 point
  14. @theo you can remove the module row (something like SessionHandlerDB) from the modules table in your database with a tool like phpmyadmin.
    1 point
  15. After clearing the cache tables from the database, you should refresh the modules (Admin > Modules, green Button "Refresh" on top).
    1 point
  16. Even overwriting only a single function (now rather two) is not really update save. It's less likely to change, but might still be subject to change.
    1 point
  17. Hey, pleini! This is not about extending a class, but maybe an easier way to do what you need. If you got a reasonable need to have some method hookable and a module is maintained, you can ask the author to make it hookable. All module writers I have talked to tried to help me when I asked and almost all of them were friendly doing that)
    1 point
  18. If your parent categories are siblings you can do this: $page->my_page_field->sort("parent.sort"); (You can do it anyway even if they're not siblings but the sort might not make much sense in that case.)
    1 point
  19. And still you where able to answer my question! sort=parent.title Is what i was looking for.
    1 point
  20. Hi Tim, What is the structure of your tree? I've read your question twice, but can't seem to understand what you mean. You can sort by subfields like: $pages->find("template=test, sort=parent.title, sort=page_field.sort"); Perhaps that is what you are looking for?
    1 point
  21. As you found, PageTable does not work inside a repeater. See this post for a handy summary of what works and what doesn't with page-type fields. Can you work around this by getting rid of the repeater and adding a PageTable option that consists of just a title field? The UI would probably be nicer with RepeaterMatrix.
    1 point
  22. Only to be sure, you have setup your site/config.php to point to your local DB? I believe it is a caching problem. If you just copied a dump from the remote DB to your local DB, you should delete all session and cache records before starting your local installation.
    1 point
  23. Hi, I've never experienced this, but the forum seems to be "full of" the issue, so you might find the answer with this search: https://www.google.hu/?client=safari#q=site:processwire.com%2Ftalk+"appears+to+be+forged" Edit: also, this message should be dealt with, I suppose: "Field: Fieldtype 'FieldtypeAdminCustomPagesSelect' does not exist"
    1 point
  24. 1 point
  25. Update: for the example above to work, I discovered I needed to have the Page Fields' Dereference in API (under the fields Details tab) set to "Single page (Page) or empty page (NullPage) when none selected"
    1 point
  26. Due to variable scoping in PHP, PW variables (like $page) are not available inside functions. You need to use: wire('page') instead.
    1 point
  27. This is because of a variable scope. Use wire('pages')->find(...)... instead. See Ryan's explanation here.
    1 point
  28. There does seem to be an issue. see i am going to be testing more later and try and submit a github issue
    1 point
  29. Anatomy of a Test (Part 1): Now that we have a test structure in place, you might be asking yourself "what sort of tests should I be writing?" In general, you should start writing tests that would test the most important aspect of your site / system. For a CMS like Processwire, that would be tests that would cover Content Integrity for example, as content is king in a CMS. Tests that would cover navigation could be another example as growing content means growing navigational possibilities. You can use tools to determine how much of your written unit tests actually test your site/system, but test coverage is beyond the scope of this post. Let's look at example content unit test to see what's actually inside. <?php /** * Created by PhpStorm. * User: FrancisChung * Date: 09/03/16 * Time: 19:06 */ namespace Test; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; //use Facebook\WebDriver\WebDriverCapabilities; require_once(__DIR__."/../../../composer/vendor/autoload.php"); class WebFingerspieleContentTest extends \PHPUnit_Framework_TestCase { protected $site; protected function setUp() { $this->site="http://localhost"; } public function testSafari() { $browser = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::safari()); $this::TestPageTitle($browser); $browser->close(); $browser->quit(); $browser = null; } public function testFirefox() { $browser = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::firefox()); $this::TestPageTitle($browser); $browser->close(); $browser = null; } public function testChrome() { $browser = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::chrome()); $this::TestPageTitle($browser); $browser->close(); $browser = null; } private function TestData() { return array( array("{$this->site}/fingerspiele/alle-fingerspiele/10-kleine-zappelmaenner/", 'Zehn kleine Zappelmänner | Fingerspiel | Sprachspielspass.de'), array("{$this->site}/fingerspiele/alle-fingerspiele/der-osterhase/", 'Der Osterhase | Fingerspiel für Kinder | Sprachspielspass.de'), array("{$this->site}/fingerspiele/alle-fingerspiele/weiss-wie-schnee/",'Weiß wie Schnee | Fingerspiel Sommer | Sprachspielspass.de'), array("{$this->site}/fingerspiele/alle-fingerspiele/ein-kleines-auto/", 'Ein kleines Auto | Fingerspiel | Sprachspielspass.de'), array("{$this->site}/fingerspiele/alle-fingerspiele/es-regnet-ganz-sacht/", 'Es regnet ganz sacht | Fingerspiel | Sprachspielspass.de'), array("{$this->site}/fingerspiele/alle-fingerspiele/das-ist-der-daumen/", 'Das ist der Daumen | Fingerspiel | Sprachspielspass.de'), array("{$this->site}/fingerspiele/alle-fingerspiele/plumps-und-platsch/", "Plumps und platsch | Fingerspiel | Sprachspielspass.de"), array("{$this->site}/fingerspiele/alle-fingerspiele/fuenf-engelchen/", 'Fünf Engelchen | Fingerspiel Winter | Sprachspielspass.de'), array("{$this->site}/fingerspiele/alle-fingerspiele/erst-kommt-die-schnecke/", 'Erst kommt die Schnecke | Fingerspiel | Sprachspielspass.de') ); // } /** * @param $browser */ private function TestPageTitle($browser) { $data = $this::TestData(); //TestFactory::PageTitleTest($data, $browser, $this); foreach($data as $testItem) { $url = $testItem[0]; $expected = $testItem[1]; $browser->get($url); $this->assertContains($expected, $browser->getTitle()); } } } Now the 2 use statements (DesiredCapabilities & Webdriver) are needed to access the Php-webdriver that we will be using to interact with Selenium. The require_once(__DIR__."/../../../composer/vendor/autoload.php") statement is to reference the Phpunit test framework that we installed via Composer (I got stuck on here for a while). Your test also needs to derive from \PHPUnit_Framework_TestCase as you can see in the above example. This is all the dependencies you need to write a Phpunit test for Processwire that runs on Phpstorm. In the next post (Part 2 of this topic), I will go through each method and explain their purpose and function.
    1 point
  30. Test Structure: Now that we've setup PHPUnit with PHPStorm, let's talk about how to structure and setup your tests. This is where people can vary in their strategy and approaches quite a bit. Would be great to see how others approach it. It's probably a good idea to isolate your tests away from your code. For example I've put my tests in /site/templates/php/tests I've structured my tests into 3 basic folders that represent the different servers my website resides on. They are core, uat and live. The core folder contains the basic core versions of my tests. They also point to my local dev server for testing purposes. The uat and live folder contains derived versions of these core tests that points to UAT & Live servers respectively. The idea with having 3 seperate folders is that you can run isolated tests on each servers easily during testing and deployment phases. Hopefully it's all been straight forward so far. I will go into detail about the tests themselves in the next post.
    1 point
  31. PHPUnit configuration in PHP Storm : This is where I got stuck for a while. The documentation surrounding this was unclear or unspecific at best. The trick was to configure PHPStorm (in preferences) to use a Custom Autoloader that you specify and also define a default configuration file, both which were not apparent to me with the available documentation I could find. The Custom Autoloader needs to point to autoload.php for Composer (hence my earlier pre-requisite of installing Phpunit via Composer). The default configuration file is a XML file that defines certain basic variables that PHPUnit in order to function. The one I've attached is a fairly basic one, with custom testsuites defined being the main difference. The custom testsuites elements will become apparent once I explain how I structured my tests. For those interested in a detailed breakdown of the XML config, there are some detailed documentation at https://phpunit.de/manual/current/en/appendixes.configuration.html. I've attached a screenshot of my preferences phpunit.xml
    1 point
  32. Fieldtype Compatibility Overview doesn't work Fieldtype PageTable inside Repeater or RepeaterMatrix Fieldtype Page + 3d Party module AdminPageEditLinks inside Repeater or RepeaterMatrix works Fieldtype RepeaterMatrix inside RepeaterMatrix Fieldtype Repeater inside Repeater Fieldtype Repeater inside RepeaterMatrix and the other way round Fieldtype Page inside Repeater or RepeaterMatrix Fieldtype PageTable inside Page and the other way round @Martijn (next post) thanks, updated list
    1 point
  33. Creating a sitemap is fairly easy in ProcessWire. The strategy we use is to get the page where we want the sitemap to start (like the homepage), print out it's children, and perform the same action on any children that themselves have children. We do this with a recursive function. Below is the contents of the sitemap.php template which demonstrates this. This example is also included in the default ProcessWire installation, but we'll go into more detail here. /site/templates/sitemap.php <?php function sitemapListPage($page) { // create a list item & link to the given page, but don't close the <li> yet echo "<li><a href='{$page->url}'>{$page->title}</a> "; // check if the page has children, if so start a nested list if($page->numChildren) { // start a nested list echo "<ul>"; // loop through the children, recursively calling this function for each foreach($page->children as $child) sitemapListPage($child); // close the nested list echo "</ul>"; } // close the list item echo "</li>"; } // include site header markup include("./head.inc"); // start the sitemap unordered list echo "<ul class='sitemap'>"; // get the homepage and start the sitemap sitemapListPage($pages->get("/")); // close the unordered list echo "</ul>"; // include site footer markup include("./foot.inc"); The resulting markup will look something like this (for the small default ProcessWire site): <ul class='sitemap'> <li><a href='/'>Home</a> <ul> <li><a href='/about/'>About</a> <ul> <li><a href='/about/child1/'>Child page example 1</a> </li> <li><a href='/about/child2/'>Child page example 2</a> </li> </ul> </li> <li><a href='/templates/'>Templates</a> </li> <li><a href='/site-map/'>Site Map</a> </li> </ul> </li> </ul> Note: to make this site map appear indented with each level, you may need to update your stylesheet with something like this: ul.sitemap li { margin-left: 2em; } The above sitemap template works well for a simple site. But what if you have some pages that have a "hidden" status? They won't appear in the sitemap, nor will any of their children. If you want them to appear, then you would want to manually add them to the what is displayed. To do this, retrieve the hidden page and send it to the sitemapListPage() function just like you did with the homepage: <?php // get the homepage and start the sitemap // (this line is included here just for placement context) sitemapListPage($pages->get("/")); // get our hidden page and include it in the site map sitemapListPage($pages->get("/some-hidden-page/")); What if your sitemap has thousands of pages? If you have a very large site, this strategy above may produce a sitemap with thousands of items and take a second or two to generate. A page with thousands of links may not be the most helpful sitemap strategy to your users, so you may want to consider alternatives. However, if you've decided you want to proceed, here is how to manage dealing with this many pages in ProcessWire. 1. First off you probably don't want to regenerate this sitemap for every pageview. As a result, you should enable caching if your template in: Admin > Setup > Templates > Sitemap > Advanced > Cache Time. I recommend setting it to one day (86400 seconds). Once you save this setting, the template will be rendered from a cache when the user is not logged in. Note that when you view it while still logged in, it's not going to use the cache… and that's okay. 2. Secondly, consider adding limits to the number of child pages you retrieve in the sitemapListPage function. It may be that you only need to list the first hundred child pages, in which case you could add a "limit=100" selector to your $page->children call: <?php // this example takes place inside the sitemapListPage function. // loop through the children, recursively calling this function for each: foreach($page->children("limit=100") as $child) sitemapListPage($child); 3. Loading thousands of pages (especially with lots of autojoined fields) may cause you to approach the memory limit of what Apache will allow for the request. If you are hitting a memory limit, you'll know it because ProcessWire will generate an error. If that happens, you need to manage your memory by freeing groups of pages once you no longer need them. Here's one strategy to use at the end of the sitemapListPage function that helps to ensure the memory allocated to the child pages is freed, making room for another thousand pages. <?php function sitemapListPage($page) { // ... everything above omitted for brevity in this example ... // close the list item echo "</li>"; // release loaded pages by telling the $pages variable to uncache them. // this will only uncache pages that are out of scope, so it's safe to use. wire('pages')->uncacheAll(); }
    1 point
  34. I had a problem here but figured it out, with Somas help
    1 point
  35. I'm guessing you don't like Laravel much? There is a reason why statics exist as a language construct, and there is a reason why we use them where we do. Though our usage is admittedly rare, there has never been a goal to "avoid statics as much as possible". The goal has been to use the tools available to us to make ProcessWire as simple, flexible and extendable, to the intended audience, as possible. It's not often that we have use for a static method in ProcessWire, but when we use them it's because they are the most appropriate solution. Keep in mind, we don't actually "need" static methods. We could certainly do without them. They just make a whole lot more sense in our context than the alternative. It's important to realize that Module is just an interface for communication between ProcessWire and the functionality you want to provide. It is not a class or even an abstract class. It provides no implementation for you (other than what you might optionally choose to extend from some other class). The only requirement for a module is that ProcessWire can ask it what it is. That comes from the Module interface's 1 static method: getModuleInfo(). Without that, it is not a Module. The ConfigurableModule interface has 1 static method: getModuleConfigInputfields(), which you can choose to delegate elsewhere if you choose. Beyond the obvious benefits, these methods are static for correctness: they are about all instances, not a specific one. The existence of the interface is not a suggestion that you implement everything in the class itself. That's entirely up to you. If the scope or philosophy of your need is such that you want to split every part of it into separate classes and files, then you should (this is what I do in FormBuilder). The Module interface facilities this. But the reality is that most modules are not of that scope, and there's rarely a tangible benefit in our context to being more verbose. But we ultimately leave that choice to the developer. A module is a singleton only if the developer specifies "singular" in his/her module definition. Otherwise you will get a new instance every time you ask for a module. I understand what you are trying to get at here. There may be someday when the configuration needs of modules increases in scope to the point where we might benefit from such an approach, but we're not near that yet. In the present, I think the majority of module cases benefit more from less verbosity and the current approach. We already have the door open to this approach, even if it's not implicit--FormBuilder uses something very similar, for example. But I would be happy to support this more implicitly as a second option for module configuration in the future. Instance types are not loaded before they are needed. They are loaded on-demand, unless the module's definition specifies "autoload". ProcessWire caches its module information so that it doesn't need to even include a module's file until ready to instantiate. It's the module developer that should make the call about whether their module is designed to be singular or multi-instance. A singular module might very well be coded differently than a multi-instance one. I don't want the consumer to have to think about this variable. I'm not opposed to making architectural changes in major releases so long as they are geared at make things simpler or easier for the users of the software. While I don't share all your opinions on how some things should work, I appreciate and respect them, and am especially glad for your interest in them. If we were to implement an architectural change to make module configuration more implicit to a separate class, I'd support it (after all, it's an approach I already take in some modules). But it would be added as an option, rather than a replacement. In terms of future major releases, I don't like breaking backwards compatibility unless absolutely necessary. But if there's a net benefit to the wider audience, then I have no problem with it. The only thing in my mind that carries that status right now is the switch to namespaces in 2.4 (and the changes that would go along with it), which I'm looking forward to collaborating on.
    1 point
  36. I recently had to setup front-end system to handle logins, password resets and changing passwords, so here's about how it was done. This should be functional code, but consider it pseudocode as you may need to make minor adjustments here and there. Please let me know if anything that doesn't compile and I'll correct it here. The template approach used here is the one I most often use, which is that the templates may generate output, but not echo it. Instead, they stuff any generated output into a variable ($page->body in this case). Then the main.php template is included at the end, and it handles sending the output. This 'main' template approach is preferable to separate head/foot includes when dealing with login stuff, because we can start sessions and do redirects before any output is actually sent. For a simple example of a main template, see the end of this post. 1. In Admin > Setup > Fields, create a new text field called 'tmp_pass' and add it to the 'user' template. This will enable us to keep track of a temporary, randomly generated password for the user, when they request a password reset. 2a. Create a new template file called reset-pass.php that has the following: /site/templates/reset-pass.php $showForm = true; $email = $sanitizer->email($input->post->email); if($email) { $u = $users->get("email=$email"); if($u->id) { // generate a random, temporary password $pass = ''; $chars = 'abcdefghjkmnopqrstuvwxyz23456789'; // add more as you see fit $length = mt_rand(9,12); // password between 9 and 12 characters for($n = 0; $n < $length; $n++) $pass .= $chars[mt_rand(0, strlen($chars)-1)]; $u->of(false); $u->tmp_pass = $pass; // populate a temporary pass to their profile $u->save(); $u->of(true); $message = "Your temporary password on our web site is: $pass\n"; $message .= "Please change it after you login."; mail($u->email, "Password reset", $message, "From: noreply@{$config->httpHost}"); $page->body = "<p>An email has been dispatched to you with further instructions.</p>"; $showForm = false; } else { $page->body = "<p>Sorry, account doesn't exist or doesn't have an email.</p>"; } } if($showForm) $page->body .= " <h2>Reset your password</h2> <form action='./' method='post'> <label>E-Mail <input type='email' name='email'></label> <input type='submit'> </form> "; // include the main HTML/markup template that outputs at least $page->body in an HTML document include('./main.php'); 2b. Create a page called /reset-pass/ that uses the above template. 3a. Create a login.php template. This is identical to other examples you may have seen, but with one major difference: it supports our password reset capability, where the user may login with a temporary password, when present. When successfully logging in with tmp_pass, the real password is changed to tmp_pass. Upon any successful authentication tmp_pass is cleared out for security. /site/templates/login.php if($user->isLoggedin()) $session->redirect('/profile/'); if($input->post->username && $input->post->pass) { $username = $sanitizer->username($input->post->username); $pass = $input->post->pass; $u = $users->get($username); if($u->id && $u->tmp_pass && $u->tmp_pass === $pass) { // user logging in with tmp_pass, so change it to be their real pass $u->of(false); $u->pass = $u->tmp_pass; $u->save(); $u->of(true); } $u = $session->login($username, $pass); if($u) { // user is logged in, get rid of tmp_pass $u->of(false); $u->tmp_pass = ''; $u->save(); // now redirect to the profile edit page $session->redirect('/profile/'); } } // present the login form $headline = $input->post->username ? "Login failed" : "Please login"; $page->body = " <h2>$headline</h2> <form action='./' method='post'> <p> <label>Username <input type='text' name='username'></label> <label>Password <input type='password' name='pass'></label> </p> <input type='submit'> </form> <p><a href='/reset-pass/'>Forgot your password?</a></p> "; include("./main.php"); // main markup template 3b. Create a /login/ page that uses the above template. 4a. Build a profile editing template that at least lets them change their password (but take it further if you want): /site/templates/profile.php // if user isn't logged in, then we pretend this page doesn't exist if(!$user->isLoggedin()) throw new Wire404Exception(); // check if they submitted a password change $pass = $input->post->pass; if($pass) { if(strlen($pass) < 6) { $page->body .= "<p>New password must be 6+ characters</p>"; } else if($pass !== $input->post->pass_confirm) { $page->body .= "<p>Passwords do not match</p>"; } else { $user->of(false); $user->pass = $pass; $user->save(); $user->of(true); $page->body .= "<p>Your password has been changed.</p>"; } } // display a password change form $page->body .= " <h2>Change password</h2> <form action='./' method='post'> <p> <label>New Password <input type='password' name='pass'></label><br> <label>New Password (confirm) <input type='password' name='pass_confirm'></label> </p> <input type='submit'> </form> <p><a href='/logout/'>Logout</a></p> "; include("./main.php"); 4b. Create a page called /profile/ that uses the template above. 5. Just to be complete, make a logout.php template and create a page called /logout/ that uses it. /site/templates/logout.php if($user->isLoggedin()) $session->logout(); $session->redirect('/'); 6. The above templates include main.php at the end. This should just be an HTML document that outputs your site's markup, like a separate head.inc or foot.inc would do, except that it's all in one file and called after the output is generated, and we leave the job of sending the output to main.php. An example of the simplest possible main.php would be: /site/templates/main.php <html> <head> <title><?=$page->title?></title> </head> <body> <?=$page->body?> </body> </html>
    1 point
×
×
  • Create New...